├── docs ├── contents.rst ├── requirements.txt ├── Cube.pdf ├── Cube.png ├── Pyramid.pdf ├── Pyramid.png ├── Vertex2d.pdf ├── Vertex2d.png ├── notched_polygon.pdf ├── notched_polygon.png ├── notched_polyhedron.png ├── notched_polygon_clip1.png ├── notched_polygon_clip2.png ├── notched_polygon_clip3.png ├── notched_polyhedron_clip3.png ├── index.rst ├── Makefile ├── make.bat ├── notched_polygon.asy ├── CMakeLists.txt ├── Vertex2d.asy ├── debugging.rst ├── intro.rst ├── polyhedral_clip_figs.py ├── Cube.asy ├── polygonal_clip_figs.py ├── Pyramid.asy ├── polygon_methods.rst ├── building.rst ├── polyhedron_methods.rst ├── conf.py.in ├── conf.py ├── concepts.rst ├── datatypes.rst └── custom_vectors.rst ├── src ├── PYB11 │ ├── CMakeLists.txt │ ├── Vertex2d.py │ ├── Vertex3d.py │ ├── Vector2d.py │ ├── Plane.py │ ├── Vector3d.py │ └── PolyClipper_PYB11.py ├── CMakeLists.txt ├── polyclipper_plane.hh ├── polyclipper_vector2d.hh ├── polyclipper_vector3d.hh ├── polyclipper_serialize.hh ├── polyclipper_adapter.hh ├── polyclipper2d.hh ├── polyclipper3d.hh ├── polyclipper_utilities.hh └── polyclipper_serializeImpl.hh ├── .gitmodules ├── test ├── test_array_vector │ ├── CMakeLists.txt │ ├── test_array_vector_2d.cc │ └── test_array_vector_3d.cc ├── stuff │ └── test_bad_clip_3d.py ├── PolyClipperTestUtilities.py ├── testPolyClipperSerialize.py └── test_serialization.py ├── cmake └── FindSphinx.cmake ├── .gitignore ├── .readthedocs.yml ├── NOTICE ├── LICENSE ├── README.md └── CMakeLists.txt /docs/contents.rst: -------------------------------------------------------------------------------- 1 | index.rst -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx-rtd-theme 2 | -------------------------------------------------------------------------------- /docs/Cube.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/Cube.pdf -------------------------------------------------------------------------------- /docs/Cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/Cube.png -------------------------------------------------------------------------------- /docs/Pyramid.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/Pyramid.pdf -------------------------------------------------------------------------------- /docs/Pyramid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/Pyramid.png -------------------------------------------------------------------------------- /docs/Vertex2d.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/Vertex2d.pdf -------------------------------------------------------------------------------- /docs/Vertex2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/Vertex2d.png -------------------------------------------------------------------------------- /docs/notched_polygon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/notched_polygon.pdf -------------------------------------------------------------------------------- /docs/notched_polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/notched_polygon.png -------------------------------------------------------------------------------- /docs/notched_polyhedron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/notched_polyhedron.png -------------------------------------------------------------------------------- /src/PYB11/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | PYB11Generator_add_module(PolyClipper INSTALL ${POLYCLIPPER_PYTHON_INSTALL}) 2 | -------------------------------------------------------------------------------- /docs/notched_polygon_clip1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/notched_polygon_clip1.png -------------------------------------------------------------------------------- /docs/notched_polygon_clip2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/notched_polygon_clip2.png -------------------------------------------------------------------------------- /docs/notched_polygon_clip3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/notched_polygon_clip3.png -------------------------------------------------------------------------------- /docs/notched_polyhedron_clip3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/PolyClipper/HEAD/docs/notched_polyhedron_clip3.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "extern/PYB11Generator"] 2 | path = extern/PYB11Generator 3 | url = https://github.com/LLNL/PYB11Generator 4 | -------------------------------------------------------------------------------- /test/test_array_vector/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test_array_vector_2d test_array_vector_2d.cc) 2 | target_link_libraries(test_array_vector_2d PUBLIC PolyClipperAPI) 3 | 4 | add_executable(test_array_vector_3d test_array_vector_3d.cc) 5 | target_link_libraries(test_array_vector_3d PUBLIC PolyClipperAPI) 6 | 7 | -------------------------------------------------------------------------------- /cmake/FindSphinx.cmake: -------------------------------------------------------------------------------- 1 | find_program(SPHINX_EXECUTABLE NAMES sphinx-build 2 | HINTS 3 | $ENV{SPHINX_DIR} 4 | PATH_SUFFIXES bin 5 | DOC "Sphinx documentation generator" 6 | ) 7 | 8 | include(FindPackageHandleStandardArgs) 9 | 10 | find_package_handle_standard_args(Sphinx DEFAULT_MSG 11 | SPHINX_EXECUTABLE 12 | ) 13 | 14 | mark_as_advanced(SPHINX_EXECUTABLE) 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | *~ 3 | *.d 4 | *.o 5 | *.pyc 6 | *.imp 7 | *.so 8 | *.dylib 9 | *.date 10 | *.dummydate 11 | *.C 12 | *.log 13 | *.silo 14 | *.job 15 | *.job.out 16 | job.out* 17 | *.swp 18 | *.orig 19 | runit 20 | spam 21 | *.a 22 | job.out 23 | job.err 24 | out.job 25 | err.job 26 | 27 | # These are files automatically generated in our configure process. 28 | src/aclocal.m4 29 | src/autom4te.cache 30 | src/config.log 31 | src/config.status 32 | src/configure 33 | 34 | # sphinx generated stuff 35 | docs/_build 36 | docs/_static 37 | docs/_templates 38 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the OS, Python version and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.12" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | 18 | # Explicitly set the version of Python and its requirements 19 | python: 20 | install: 21 | - requirements: docs/requirements.txt 22 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. PolyClipper documentation master file, created by 2 | sphinx-quickstart on Tue Nov 6 11:06:43 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to PolyClipper's documentation! 7 | ======================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 3 11 | :caption: Contents: 12 | 13 | intro.rst 14 | building.rst 15 | concepts.rst 16 | datatypes.rst 17 | polygon_methods.rst 18 | polyhedron_methods.rst 19 | custom_vectors.rst 20 | debugging.rst 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | * :ref:`modindex` 27 | * :ref:`search` 28 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = #-D imgmath_latex=/usr/tce/packages/texlive/texlive-2016/2016/bin/x86_64-linux/latex 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /test/stuff/test_bad_clip_3d.py: -------------------------------------------------------------------------------- 1 | import os, os.path 2 | from PolyClipper import * 3 | 4 | for filename in os.listdir("3d"): 5 | if filename.endswith(".bin"): 6 | print("--------------------------------------------------------------------------------") 7 | print("Reading ", filename) 8 | with open(os.path.join("3d", filename), 'rb') as f: 9 | buf = f.read() 10 | 11 | poly, itr = deserialize_Polyhedron(0, buf) 12 | planes, itr = deserialize_vector_of_Plane3d(itr, buf) 13 | 14 | print("Read poly:\n", polyhedron2string(poly)) 15 | print(" moments: ", moments(poly)) 16 | print("Read planes:\n", planes) 17 | 18 | clipPolyhedron(poly, planes) 19 | 20 | print("After clipping:\n", polyhedron2string(poly)) 21 | print(" moments: ", moments(poly)) 22 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/notched_polygon.asy: -------------------------------------------------------------------------------- 1 | // We'll draw a hexagon with one vertex set highlighted 2 | 3 | size(400); 4 | import geometry; 5 | 6 | real A = 1; // edge length 7 | int nfaces = 6; // hexagon 8 | int vann = 2; // Vertex were going to call out and annotate 9 | 10 | real dot_size = 10; 11 | real arrow_size = 10; 12 | real line_size = 2; 13 | 14 | pair[] verts = {(0,0), (4,0), (4,2), (3,2), (2,1), (1,2), (0,2)}; // Taken from the 2D tests 15 | verts.cyclic = true; // indexing is periodic! 16 | 17 | pair[] coord_pos = {NE, NW, SW, SE, S, SW, SE}; 18 | pair[] lab_pos = {SW, SE, NE, N, N, N, NW}; 19 | for (int i = 0; i < verts.length; ++i) { 20 | draw(verts[i]--verts[i+1], linewidth(line_size)); 21 | dot(verts[i], black+linewidth(dot_size)); 22 | label(format(i), verts[i], 3*lab_pos[i]); 23 | label("(" + format(verts[i].x) + ", " + format(verts[i].y) + ")", verts[i], 3*coord_pos[i], magenta); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # Build PolyClipper packages 3 | #------------------------------------------------------------------------------- 4 | include_directories(.) 5 | 6 | # PolyClipper itself is entirely C++ headers 7 | add_library(PolyClipperAPI INTERFACE) 8 | target_include_directories( 9 | PolyClipperAPI 10 | INTERFACE $ 11 | $ 12 | ) 13 | 14 | set(PolyClipper_headers 15 | polyclipper2d.hh 16 | polyclipper2dImpl.hh 17 | polyclipper3d.hh 18 | polyclipper3dImpl.hh 19 | polyclipper_adapter.hh 20 | polyclipper_plane.hh 21 | polyclipper_serialize.hh 22 | polyclipper_serializeImpl.hh 23 | polyclipper_utilities.hh 24 | polyclipper_vector2d.hh 25 | polyclipper_vector3d.hh) 26 | install(FILES ${PolyClipper_headers} 27 | DESTINATION ${POLYCLIPPER_INSTALL_DIR}) 28 | 29 | # Are we building the Python bindings? 30 | if(${POLYCLIPPER_MODULE_GEN}) 31 | add_subdirectory(PYB11) 32 | endif() 33 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Sphinx) 2 | message("-- Sphinx ${SPHINX_EXECUTABLE}") 3 | 4 | if(NOT SPHINX_EXECUTABLE STREQUAL "SPHINX_EXECUTABLE-NOTFOUND") 5 | if(NOT DEFINED SPHINX_THEME) 6 | set(SPHINX_THEME default) 7 | endif() 8 | 9 | if(NOT DEFINED SPHINX_THEME_DIR) 10 | set(SPHINX_THEME_DIR) 11 | endif() 12 | 13 | # configured documentation tools and intermediate build results 14 | set(BINARY_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_build") 15 | 16 | # Sphinx cache with pickled ReST documents 17 | set(SPHINX_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_doctrees") 18 | 19 | # HTML output directory 20 | set(SPHINX_HTML_DIR "${CMAKE_CURRENT_BINARY_DIR}/html") 21 | 22 | configure_file( 23 | "${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in" 24 | "${BINARY_BUILD_DIR}/conf.py" 25 | @ONLY) 26 | 27 | add_custom_target(PolyClipper_docs ALL 28 | ${SPHINX_EXECUTABLE} 29 | -q -b html 30 | -c "${BINARY_BUILD_DIR}" 31 | -d "${SPHINX_CACHE_DIR}" 32 | "${CMAKE_CURRENT_SOURCE_DIR}" 33 | "${SPHINX_HTML_DIR}" 34 | COMMENT "Building HTML documentation with Sphinx") 35 | endif() 36 | -------------------------------------------------------------------------------- /docs/Vertex2d.asy: -------------------------------------------------------------------------------- 1 | // We'll draw a hexagon with one vertex set highlighted 2 | 3 | size(150); 4 | import geometry; 5 | 6 | real A = 1; // edge length 7 | int nfaces = 6; // hexagon 8 | int vann = 2; // Vertex were going to call out and annotate 9 | 10 | real dot_size = 10; 11 | real arrow_size = 10; 12 | real dtheta = 2.0*pi/nfaces; 13 | pair a = (0, 0); 14 | pair[] verts = {a}; 15 | 16 | for (int i = 0; i < nfaces; ++i) { 17 | real theta = i*dtheta; 18 | pair b = a + (A*cos(theta), A*sin(theta)); 19 | 20 | // Draw the arrow 21 | if (i >= vann-1 && i <= vann) { 22 | draw(a--b, red+linewidth(1.5), MidArrow(arrow_size)); 23 | dot(a, red+linewidth(dot_size)); 24 | } else { 25 | draw(a--b, black, MidArrow(arrow_size)); 26 | dot(a, black+linewidth(dot_size)); 27 | } 28 | 29 | // Put a labeled point as appropriate. 30 | real t = pi + (i+1)*dtheta; 31 | pair labpos = (2*cos(t), 2*sin(t)); 32 | label(format(i), a, labpos); 33 | if (i == vann) { 34 | dot(a, blue+linewidth(dot_size)); 35 | } else if (i >= vann-1 && i <= vann+1) { 36 | dot(a, red+linewidth(dot_size)); 37 | } else { 38 | dot(a, black+linewidth(dot_size)); 39 | } 40 | 41 | a = b; 42 | verts.push(b); 43 | } 44 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This work was produced under the auspices of the U.S. Department of 2 | Energy by Lawrence Livermore National Laboratory under Contract 3 | DE-AC52-07NA27344. 4 | 5 | This work was prepared as an account of work sponsored by an agency of 6 | the United States Government. Neither the United States Government nor 7 | Lawrence Livermore National Security, LLC, nor any of their employees 8 | makes any warranty, expressed or implied, or assumes any legal liability 9 | or responsibility for the accuracy, completeness, or usefulness of any 10 | information, apparatus, product, or process disclosed, or represents that 11 | its use would not infringe privately owned rights. 12 | 13 | Reference herein to any specific commercial product, process, or service 14 | by trade name, trademark, manufacturer, or otherwise does not necessarily 15 | constitute or imply its endorsement, recommendation, or favoring by the 16 | United States Government or Lawrence Livermore National Security, LLC. 17 | 18 | The views and opinions of authors expressed herein do not necessarily 19 | state or reflect those of the United States Government or Lawrence 20 | Livermore National Security, LLC, and shall not be used for advertising 21 | or product endorsement purposes. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Lawrence Livermore National Security, LLC 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /docs/debugging.rst: -------------------------------------------------------------------------------- 1 | ############################################ 2 | A few notes about debugging with PolyClipper 3 | ############################################ 4 | 5 | PolyClipper can be built for debugging using the standard Cmake configuration ``-DCMAKE_BUILD_TYPE=Debug``. When configured this way, this activates a custom assertion method in PolyClipper (``PCASSERT``) defined in ``polyclipper_utilities.hh``. Since PolyClipper in C++ is entirely inlined header functions, these assertions are active for any compilation using including PolyClipper headers that do not define the ``-DNDEBUG`` option on the compile line. 6 | 7 | In general compiling with these assertions active will have a serious runtime cost. However, when trying to track down bugs or problems using PolyCliperr code these internal checks can be invaluable. There are two aspects in particular developers should be aware of. 8 | 9 | * PolyClipper defines a custom exception type, ``PolyClipperError``, which can be screened for with standard C++ try/catch clauses. This is the exception that will be raised by any ``PCASSERT`` violation. 10 | 11 | * PolyClipper includes some handy serialization methods for packaging up polygons/polyhedra and planes when exceptions occur. Many of the ``PCASSERT`` statements will result in this serialized state being written out to a local file (in binary format), which can then be picked up by standalone PolyClipper to reproduce the error condition. The source file ``test/stuff/test_bad_clip.py`` is one example of how to pick up one of these serialized binary blobs using standalone PolyClipper's Python interface to reproduce 3D polyhedral clipping problems. When reporting problems with PolyClipper these blobs capturing the exact input that reproduces the error can be extremely useful in getting such problems resolved. 12 | 13 | -------------------------------------------------------------------------------- /src/polyclipper_plane.hh: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // Plane. 3 | //------------------------------------------------------------------------------ 4 | #ifndef __PolyClipper_Plane__ 5 | #define __PolyClipper_Plane__ 6 | 7 | #include 8 | 9 | namespace PolyClipper { 10 | 11 | template 12 | struct Plane { 13 | using Vector = typename VA::VECTOR; 14 | double dist; // Signed distance to the origin 15 | Vector normal; // Unit normal 16 | int ID; // ID for the plane, used to label vertices 17 | Plane() : dist(0.0), normal(VA::Vector(1.0, 0.0, 0.0)), ID(std::numeric_limits::min()) {} 18 | Plane(const double d, const Vector& nhat) : dist(d), normal(nhat), ID(std::numeric_limits::min()) {} 19 | Plane(const double d, const Vector& nhat, const int id) : dist(d), normal(nhat), ID(id) {} 20 | Plane(const Vector& p, const Vector& nhat) : dist(VA::dot(VA::neg(p), nhat)), normal(nhat), ID(std::numeric_limits::min()) {} 21 | Plane(const Vector& p, const Vector& nhat, const int id) : dist(VA::dot(VA::neg(p), nhat)), normal(nhat), ID(id) {} 22 | Plane(const Plane& rhs) : dist(rhs.dist), normal(rhs.normal), ID(rhs.ID) {} 23 | Plane& operator=(const Plane& rhs) { dist = rhs.dist; normal = rhs.normal; ID = rhs.ID; return *this; } 24 | bool operator==(const Plane& rhs) const { return (dist == rhs.dist and VA::equal(normal, rhs.normal)); } 25 | bool operator!=(const Plane& rhs) const { return not (*this == rhs); } 26 | bool operator< (const Plane& rhs) const { return (dist < rhs.dist); } 27 | bool operator> (const Plane& rhs) const { return (dist > rhs.dist); } 28 | }; 29 | 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | ############ 2 | Introduction 3 | ############ 4 | 5 | PolyClipper is a C++ reimplementation of the geometric clipping operations in the `R3D `_ library originally written by Devon Powell, as documented in the paper 6 | `Powell & Abell (2015) `_. 7 | 8 | The main focus here is on clipping polygons (in 2D :math:`(x,y)` coordinates) and polyhedra (in 3D :math:`(x,y,z)` coordinates) with planes, returning new polygons/polyhedra as the result of this clipping. The input polygons/polyhedra may be non-convex and arbitrarily complex, but the only clipping operation supported is with planes. This is equivalent to intersecting one arbitrary (not necessarily convex) polygon/polyhedron with a convex polygon/polyhedron. 9 | 10 | PolyClipper reimplements these clipping operations from R3D for two reasons: 11 | * PolyClipper removes the hard-coded size limitations of R3D on the number of vertices/complexity of the polygons and polyhedra. 12 | * PolyClipper also removes the assumption that each vertex in 3D has exactly three neighbors (as well as the related limitation of two neighbors in 2D) -- the number of neighbors per vertex is now arbitrary. This also removes the complexity of requiring degenerate/redundant vertices. 13 | 14 | Note PolyClipper currently does not provide the generalized voxelization or arbitrary integrals over polygons/polyhedra as provided in R3D. These would be straightforward to add, but were not necessary for the authors needs from the library, which is to generalize the clipping algorithms. The only method of this sort provided by PolyClipper is the ability to do the zeroth and first moment integrals over the polygons/polyhedra. 15 | 16 | PolyClipper currently provides both C++ and Python interfaces. 17 | 18 | License 19 | ------- 20 | 21 | PolyClipper is released under the `BSD license `_. 22 | 23 | LLNL-CODE-811676 24 | 25 | SPDX-License-Identifier: BSD-3 26 | -------------------------------------------------------------------------------- /docs/polyhedral_clip_figs.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("../test") 3 | 4 | from math import * 5 | from PolyClipper import * 6 | from PolyClipperTestUtilities import * 7 | from testPolyClipper3d import * 8 | 9 | #------------------------------------------------------------------------------- 10 | # Unclipped object 11 | #------------------------------------------------------------------------------- 12 | poly = Polyhedron() 13 | initializePolyhedron(poly, notched_points, notched_neighbors) 14 | writePolyOBJ(poly, "notched_polyhedron.obj") 15 | print("Starting poly: ", list(poly)) 16 | 17 | #------------------------------------------------------------------------------- 18 | # Clip 1 19 | #------------------------------------------------------------------------------- 20 | poly = Polyhedron() 21 | initializePolyhedron(poly, notched_points, notched_neighbors) 22 | planes = [Plane3d(Vector3d(3, 1, 0), Vector3d(-1, 0.5, -1.5).unitVector(), 10)] 23 | clipPolyhedron(poly, planes) 24 | writePolyOBJ(poly, "notched_polyhedron_clip1.obj") 25 | print("Single clip: ", list(poly)) 26 | 27 | #------------------------------------------------------------------------------- 28 | # Clip 2 29 | #------------------------------------------------------------------------------- 30 | poly = Polyhedron() 31 | initializePolyhedron(poly, notched_points, notched_neighbors) 32 | planes = [Plane3d(Vector3d(3, 1, 0), Vector3d(1, -0.5, 1.5).unitVector(), 10)] 33 | clipPolyhedron(poly, planes) 34 | writePolyOBJ(poly, "notched_polyhedron_clip2.obj") 35 | print("Reverse clip: ", list(poly)) 36 | 37 | #------------------------------------------------------------------------------- 38 | # Clip 3 39 | #------------------------------------------------------------------------------- 40 | poly = Polyhedron() 41 | initializePolyhedron(poly, notched_points, notched_neighbors) 42 | planes = [Plane3d(Vector3d(3, 1, 0), Vector3d(-1, 0.5, -1.5).unitVector(), 10), 43 | Plane3d(Vector3d(1, 1, 0), Vector3d(1, 0, -1).unitVector(), 30)] 44 | clipPolyhedron(poly, planes) 45 | writePolyOBJ(poly, "notched_polyhedron_clip3.obj") 46 | print("Double clip: ", list(poly)) 47 | -------------------------------------------------------------------------------- /src/polyclipper_vector2d.hh: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 2D Vector 3 | //------------------------------------------------------------------------------ 4 | #ifndef __PolyClipper_Vector2d__ 5 | #define __PolyClipper_Vector2d__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace PolyClipper { 12 | 13 | struct Vector2d { 14 | double x, y; 15 | Vector2d(): x(0.0), y(0.0) {} 16 | Vector2d(double X, double Y, double Z = 0.0) : x(X), y(Y) {} // Z dummied out for interface consistency with 3D 17 | bool operator==(const Vector2d& rhs) const { return x == rhs.x and y == rhs.y; } 18 | double dot(const Vector2d& rhs) const { return x*rhs.x + y*rhs.y; } 19 | double crossmag(const Vector2d& rhs) const { return x*rhs.y - y*rhs.x; } 20 | double magnitude2() const { return x*x + y*y; } 21 | double magnitude() const { return std::sqrt(x*x + y*y); } 22 | Vector2d& operator*=(const double rhs) { x *= rhs; y *= rhs; return *this; } 23 | Vector2d& operator/=(const double rhs) { x /= rhs; y /= rhs; return *this; } 24 | Vector2d operator+=(const Vector2d rhs) { x += rhs.x; y += rhs.y; return *this; } 25 | Vector2d operator-=(const Vector2d rhs) { x -= rhs.x; y -= rhs.y; return *this; } 26 | Vector2d operator*(const double rhs) const { return Vector2d(rhs*x, rhs*y); } 27 | Vector2d operator/(const double rhs) const { return Vector2d(x/rhs, y/rhs); } 28 | Vector2d operator+(const Vector2d rhs) const { return Vector2d(x + rhs.x, y + rhs.y); } 29 | Vector2d operator-(const Vector2d rhs) const { return Vector2d(x - rhs.x, y - rhs.y); } 30 | Vector2d operator-() const { return Vector2d(-x, -y); } 31 | Vector2d unitVector() const { 32 | const auto mag = this->magnitude(); 33 | return (mag > 0.0 ? Vector2d(x/mag, y/mag) : Vector2d(1.0, 0.0)); 34 | } 35 | std::array triple() const { return {x, y, 0.0}; } 36 | void set_triple(const std::array& vals) { x = vals[0]; y = vals[1]; } 37 | }; 38 | 39 | inline Vector2d operator*(const double lhs, const Vector2d& rhs) { return rhs*lhs; } 40 | 41 | } 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /docs/Cube.asy: -------------------------------------------------------------------------------- 1 | // This example shows a cube in PolyClipper topology 2 | 3 | import geometry; 4 | import three; 5 | 6 | size3(150); 7 | 8 | real A = 1; // edge length 9 | int vann = 6; // Vertex we're going to call out 10 | int vback = 0; // Vertex behind the cube 11 | 12 | real dot_size = 10; 13 | real arrow_size = 10; 14 | 15 | // Cube geometry 16 | triple[] coords = {(0,0,0), (A,0,0), (A,A,0), (0,A,0), 17 | (0,0,A), (A,0,A), (A,A,A), (0,A,A)}; 18 | int[][] cube_neighbors = {{1, 4, 3}, 19 | {5, 0, 2}, 20 | {3, 6, 1}, 21 | {7, 2, 0}, 22 | {5, 7, 0}, 23 | {1, 6, 4}, 24 | {5, 2, 7}, 25 | {4, 6, 3}}; 26 | 27 | // Draw the edges and such 28 | for (int i = 0; i < coords.length; ++i) { 29 | 30 | // Draw the vertex dots 31 | bool bluedot = (i == vann); 32 | bool reddot = false; 33 | for (int kk = 0; kk < cube_neighbors[vann].length; ++kk) { 34 | reddot = reddot || (i == cube_neighbors[vann][kk]); 35 | } 36 | if (bluedot) { 37 | dot(coords[i], blue+linewidth(dot_size)); 38 | } else if (reddot) { 39 | dot(coords[i], red+linewidth(dot_size)); 40 | } else { 41 | dot(coords[i], linewidth(dot_size)); 42 | } 43 | label(format(i), coords[i], 2*SE); 44 | 45 | // Draw the lines between vertices 46 | for (int k = 0; k < cube_neighbors[i].length; ++k) { 47 | int j = cube_neighbors[i][k]; 48 | 49 | bool redline = (i == vann || j == vann); 50 | for (int kk = 0; kk < cube_neighbors[vann].length; ++kk) { 51 | redline = redline || (j == vann); 52 | } 53 | bool dashedline = (i == vback || j == vback); 54 | 55 | if (dashedline) { 56 | draw(coords[i]--coords[j], dashed+linewidth(1)); 57 | } else if (redline) { 58 | draw(coords[i]--coords[j], solid+red+linewidth(1.5)); 59 | } else { 60 | draw(coords[i]--coords[j], solid+linewidth(1.5)); 61 | } 62 | } 63 | } 64 | 65 | // Draw a directional indicator for how we specify points around a vertex. 66 | triple[] pts = {0.6*coords[vann] + 0.4*coords[cube_neighbors[vann][0]], 67 | 0.6*coords[vann] + 0.4*coords[cube_neighbors[vann][1]], 68 | 0.6*coords[vann] + 0.4*coords[cube_neighbors[vann][2]]}; 69 | draw(pts[0]..pts[1]..pts[2], Arrow3(20)); 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PolyClipper 2 | ============== 3 | 4 | PolyClipper is a C++ reimplementation of the geometric clipping operations in the [R3D](https://github.com/devonmpowell/r3d) library originally written by Devon Powell, as documented in the paper 5 | [Powell & Abell (2015)](http://www.sciencedirect.com/science/article/pii/S0021999115003563). 6 | 7 | The main focus here is on clipping polygons (in 2D (x,y) coordinates) and polyhedra (in 3D (x,y,z) coordinates) with planes, returning new polygons/polyhedra as the result of this clipping. The input polygons/polyhedra may be non-convex and arbitrarily complex, but the only clipping operation supported is with planes. This is equivalent to intersecting one arbitrary (not necessarily convex) polygon/polyhedron with a convex polygon/polyhedron. 8 | 9 | PolyClipper reimplements these clipping operations from R3D for two reasons: 10 | * PolyClipper removes the hard-coded size limitations of R3D on the number of vertices/complexity of the polygons and polyhedra. 11 | * PolyClipper also removes the assumption that each vertex in 3D has exactly three neighbors (as well as the related limitation of two neighbors in 2D) -- the number of neighbors per vertex is now arbitrary. This also removes the complexity of requiring degenerate/redundant vertices. 12 | 13 | Note PolyClipper currently does not provide the generalized voxelization or arbitrary integrals over polygons/polyhedra as provided in R3D. These would be straightforward to add, but were not necessary for the authors needs from the library, which is to generalize the clipping algorithms. The only method of this sort provided by PolyClipper is the ability to do the zeroth and first moment integrals over the polygons/polyhedra. 14 | 15 | PolyClipper currently provides both C++ and Python interfaces. 16 | 17 | Documentation 18 | ------------- 19 | 20 | PolyClipper is documented at [readthedocs](https://PolyClipper.readthedocs.io/en/latest/). 21 | 22 | Note the source for this documentation is embedded in the PolyClipper repository under docs/. 23 | 24 | Contributions 25 | ------------- 26 | 27 | Contributions are welcome, and should be provided as pull requests to the main repository. Note all contributions must be provided under the same license for distribution (in this case the BSD license). 28 | 29 | License 30 | ------- 31 | 32 | PolyClipper is released under the [BSD license](https://github.com/LLNL/PolyClipper/blob/master/LICENSE). 33 | 34 | LLNL-CODE-811676 35 | 36 | SPDX-License-Identifier: BSD-3 37 | -------------------------------------------------------------------------------- /src/polyclipper_vector3d.hh: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3D Vector 3 | //------------------------------------------------------------------------------ 4 | #ifndef __PolyClipper_Vector3d__ 5 | #define __PolyClipper_Vector3d__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace PolyClipper { 12 | 13 | struct Vector3d { 14 | double x, y, z; 15 | Vector3d(): x(0.0), y(0.0), z(0.0) {} 16 | Vector3d(double X, double Y, double Z): x(X), y(Y), z(Z) {} 17 | bool operator==(const Vector3d& rhs) const { return x == rhs.x and y == rhs.y and z == rhs.z; } 18 | double dot(const Vector3d& rhs) const { return x*rhs.x + y*rhs.y + z*rhs.z; } 19 | Vector3d cross(const Vector3d& rhs) const { return Vector3d(y*rhs.z - z*rhs.y, 20 | z*rhs.x - x*rhs.z, 21 | x*rhs.y - y*rhs.x); } 22 | double magnitude2() const { return x*x + y*y + z*z; } 23 | double magnitude() const { return std::sqrt(x*x + y*y + z*z); } 24 | Vector3d& operator*=(const double rhs) { x *= rhs; y *= rhs; z *= rhs; return *this; } 25 | Vector3d& operator/=(const double rhs) { x /= rhs; y /= rhs; z /= rhs; return *this; } 26 | Vector3d operator+=(const Vector3d rhs) { x += rhs.x; y += rhs.y; z += rhs.z; return *this; } 27 | Vector3d operator-=(const Vector3d rhs) { x -= rhs.x; y -= rhs.y; z -= rhs.z; return *this; } 28 | Vector3d operator*(const double rhs) const { return Vector3d(rhs*x, rhs*y, rhs*z); } 29 | Vector3d operator/(const double rhs) const { return Vector3d(x/rhs, y/rhs, z/rhs); } 30 | Vector3d operator+(const Vector3d rhs) const { return Vector3d(x + rhs.x, y + rhs.y, z + rhs.z); } 31 | Vector3d operator-(const Vector3d rhs) const { return Vector3d(x - rhs.x, y - rhs.y, z - rhs.z); } 32 | Vector3d operator-() const { return Vector3d(-x, -y, -z); } 33 | Vector3d unitVector() const { 34 | const auto mag = this->magnitude(); 35 | return (mag > 0.0 ? Vector3d(x/mag, y/mag, z/mag) : Vector3d(1.0, 0.0, 0.0)); 36 | } 37 | std::array triple() const { return {x, y, z}; } 38 | void set_triple(const std::array& vals) { x = vals[0]; y = vals[1]; z = vals[2]; } 39 | }; 40 | 41 | inline Vector3d operator*(const double lhs, const Vector3d& rhs) { return rhs*lhs; } 42 | 43 | } 44 | 45 | #endif 46 | 47 | -------------------------------------------------------------------------------- /docs/polygonal_clip_figs.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("../test") 3 | 4 | from math import * 5 | from PolyClipper import * 6 | from PolyClipperTestUtilities import * 7 | from testPolyClipper2d import * 8 | 9 | #------------------------------------------------------------------------------- 10 | # Make a non-convex notched thingy. 11 | 6 5 3 2 12 | # |------------\ /-----------| 13 | # | \ / | 14 | # | \/ | 15 | # | 4 | 16 | # | | 17 | # |------------------------------ 18 | # 0 1 19 | notched_points = [Vector2d(*coords) 20 | for coords in [(0,0), (4,0), (4,2), (3,2), (2,1), (1,2), (0,2)]] 21 | n = len(notched_points) 22 | notched_neighbors = [[(i - 1) % n, (i + 1) % n] for i in range(n)] 23 | 24 | #------------------------------------------------------------------------------- 25 | # Unclipped object 26 | #------------------------------------------------------------------------------- 27 | poly = Polygon() 28 | initializePolygon(poly, notched_points, notched_neighbors) 29 | writePolyOBJ(poly, "notched_polygon.obj") 30 | print("Starting poly: ", list(poly)) 31 | 32 | #------------------------------------------------------------------------------- 33 | # Clip 1 34 | #------------------------------------------------------------------------------- 35 | poly = Polygon() 36 | initializePolygon(poly, notched_points, notched_neighbors) 37 | planes = [Plane2d(Vector2d(3, 1), Vector2d(-1, 0.5).unitVector(), 10)] 38 | clipPolygon(poly, planes) 39 | writePolyOBJ(poly, "notched_polygon_clip1.obj") 40 | print("Single clip: ", list(poly)) 41 | 42 | #------------------------------------------------------------------------------- 43 | # Clip 2 44 | #------------------------------------------------------------------------------- 45 | poly = Polygon() 46 | initializePolygon(poly, notched_points, notched_neighbors) 47 | planes = [Plane2d(Vector2d(3, 1), Vector2d(1, -0.5).unitVector(), 20)] 48 | clipPolygon(poly, planes) 49 | writePolyOBJ(poly, "notched_polygon_clip2.obj") 50 | print("Reverse clip: ", list(poly)) 51 | 52 | #------------------------------------------------------------------------------- 53 | # Clip 3 54 | #------------------------------------------------------------------------------- 55 | poly = Polygon() 56 | initializePolygon(poly, notched_points, notched_neighbors) 57 | planes = [Plane2d(Vector2d(3, 1), Vector2d(-1, 0.5).unitVector(), 10), 58 | Plane2d(Vector2d(2, 1.1), Vector2d(1, 5).unitVector(), 30)] 59 | clipPolygon(poly, planes) 60 | writePolyOBJ(poly, "notched_polygon_clip3.obj") 61 | print("Double clip: ", list(poly)) 62 | -------------------------------------------------------------------------------- /test/PolyClipperTestUtilities.py: -------------------------------------------------------------------------------- 1 | import PolyClipper 2 | from collections.abc import Iterable 3 | 4 | #------------------------------------------------------------------------------- 5 | # Fuzzy comparisons. 6 | #------------------------------------------------------------------------------- 7 | def fuzzyEqual(lhs, rhs, 8 | fuzz = 1.0e-5): 9 | if isinstance(lhs, Iterable): 10 | assert isinstance(rhs, Iterable) and len(lhs) == len(rhs) 11 | return min([fuzzyEqual(x, y, fuzz) for (x, y) in zip(lhs, rhs)]) 12 | else: 13 | return abs(lhs - rhs)/max(1.0, abs(lhs) + abs(rhs)) < fuzz; 14 | 15 | #------------------------------------------------------------------------------- 16 | # Write an OBJ (vertex-facet labeled) shape file from a polygon/polyhedron. 17 | # NOTE!! These things seem to count from 1 rather than 0 for indices! 18 | #------------------------------------------------------------------------------- 19 | def writePolyOBJ(poly, filename, forceTriangles=False): 20 | with open(filename, "w") as f: 21 | facets = PolyClipper.extractFaces(poly) 22 | 23 | if isinstance(poly, PolyClipper.Polygon): 24 | for v in poly: 25 | f.write("v %g %g 0.0\n" % (v.position.x, v.position.y)) 26 | 27 | # There could be multiple loops in a polygon, so break 28 | # those up into individual facets for obj output. 29 | # First, sort topologically so the loops are contiguous 30 | for i in range(len(facets) - 1): 31 | v1 = facets[i][1] 32 | j = i + 1 33 | while j < len(facets) and v1 != facets[j][0]: 34 | j += 1 35 | if j < len(facets): 36 | facets[i+1], facets[j] = facets[j], facets[i+1] 37 | 38 | # Now we can write the facets. 39 | f.write("f") 40 | i = 0 41 | for i in range(len(facets)): 42 | f.write(" %i" % (facets[i][0] + 1)) 43 | if ((i + 1) < len(facets) and 44 | facets[i][1] != facets[i+1][0]): 45 | f.write("\nf") 46 | f.write("\n") 47 | 48 | else: 49 | for v in poly: 50 | f.write("v %g %g %g\n" % (v.position.x, v.position.y, v.position.z)) 51 | 52 | for facet in facets: 53 | f.write("f") 54 | if forceTriangles and len(facet > 3): 55 | for j in range(1, len(facet)-1): 56 | f.write(" %i %i %i\n" % (0, j, j+1)) 57 | else: 58 | for i in facet: 59 | f.write(" %i" % (i + 1)) 60 | f.write("\n") 61 | 62 | return 63 | 64 | -------------------------------------------------------------------------------- /src/PYB11/Vertex2d.py: -------------------------------------------------------------------------------- 1 | from PYB11Generator import * 2 | 3 | @PYB11cppname("Vertex2d<>") 4 | class Vertex2d: 5 | '''The PolyClipper 2D (x,y) vertex type. 6 | 7 | Vertex2d is used to encode Polygons, so that PolyClipper Polygons are simply 8 | collections of vertices. A Vertex2d consists of a position and a pair of 9 | neighbor Vertex2d indices (a,b), which represent the (previous, next) vertices 10 | going around a polygon in counterclockwise order. 11 | 12 | New vertices are created by clipping operations, and Vertex maintains a set of 13 | the plane IDs that have touched/clipped this vertex. 14 | 15 | Vertex2d also maintains a "comp" and "ID" attributes, which are primarily for 16 | internal usage during PolyClipper clipping operations. 17 | ''' 18 | 19 | PYB11typedefs = """ 20 | typedef Vector2d Vector; 21 | """ 22 | 23 | #--------------------------------------------------------------------------- 24 | # Constructors 25 | #--------------------------------------------------------------------------- 26 | def pyinit0(self): 27 | "Default constructor" 28 | 29 | def pyinit1(self, 30 | pos = "const Vector&"): 31 | "Construct with a position" 32 | 33 | def pyinit2(self, 34 | pos = "const Vector&", 35 | c = "const int"): 36 | "Construct with a position and initial 'comp' value" 37 | 38 | def pyinit3(self, 39 | rhs = "const Vertex2d<>&"): 40 | "Copy constructor" 41 | 42 | #--------------------------------------------------------------------------- 43 | # Operators 44 | #--------------------------------------------------------------------------- 45 | def __eq__(self): 46 | return 47 | 48 | #--------------------------------------------------------------------------- 49 | # Methods 50 | #--------------------------------------------------------------------------- 51 | @PYB11implementation(''' 52 | [](const Vertex2d<>& self) { 53 | auto result = "{pos=(" + std::to_string(self.position.x) + " " + std::to_string(self.position.y) + 54 | "), neighbors=(" + std::to_string(self.neighbors.first) + " " + std::to_string(self.neighbors.second) + 55 | "), ID=" + std::to_string(self.ID) + 56 | ", clips=( "; 57 | for (const auto x: self.clips) result += std::to_string(x) + " "; 58 | result += ")}"; 59 | return result; 60 | }''') 61 | def __repr__(self): 62 | return 63 | 64 | #--------------------------------------------------------------------------- 65 | # Attributes 66 | #--------------------------------------------------------------------------- 67 | position = PYB11readwrite() 68 | neighbors = PYB11readwrite() 69 | comp = PYB11readwrite() 70 | ID = PYB11readwrite() 71 | clips = PYB11readwrite() 72 | -------------------------------------------------------------------------------- /docs/Pyramid.asy: -------------------------------------------------------------------------------- 1 | // This example shows a cube in PolyClipper topology 2 | 3 | import geometry; 4 | import three; 5 | 6 | size3(150); 7 | 8 | real A = 1; // edge length 9 | int vann = 4; // Vertex we're going to call out 10 | int vback = 0; // Vertex behind the cube 11 | 12 | real dot_size = 10; 13 | real arrow_size = 20; 14 | 15 | // Pyramid geometry 16 | triple[] coords = {(0,0,0), (A,0,0), (A,A,0), (0,A,0), // Base 17 | (0.5*A, 0.5*A ,A)}; // Apex 18 | int[][] cube_neighbors = {{1, 4, 3}, 19 | {2, 4, 0}, 20 | {3, 4, 1}, 21 | {0, 4, 2}, 22 | {0, 1, 2, 3}}; 23 | 24 | // Draw the edges and such 25 | for (int i = 0; i < coords.length; ++i) { 26 | 27 | // Draw the vertex dots 28 | bool bluedot = (i == vann); 29 | bool reddot = false; 30 | for (int kk = 0; kk < cube_neighbors[vann].length; ++kk) { 31 | reddot = reddot || (i == cube_neighbors[vann][kk]); 32 | } 33 | if (bluedot) { 34 | dot(coords[i], blue+linewidth(dot_size)); 35 | } else if (reddot) { 36 | dot(coords[i], red+linewidth(dot_size)); 37 | } else { 38 | dot(coords[i], linewidth(dot_size)); 39 | } 40 | label(format(i), coords[i], 2*E); 41 | 42 | // Draw the lines between vertices 43 | for (int k = 0; k < cube_neighbors[i].length; ++k) { 44 | int j = cube_neighbors[i][k]; 45 | 46 | bool redline = (i == vann || j == vann); 47 | for (int kk = 0; kk < cube_neighbors[vann].length; ++kk) { 48 | redline = redline || (j == vann); 49 | } 50 | bool dashedline = (i == vback || j == vback); 51 | 52 | if (dashedline && redline) { 53 | draw(coords[i]--coords[j], dashed+red+linewidth(1)); 54 | } else if (dashedline) { 55 | draw(coords[i]--coords[j], dashed+linewidth(1)); 56 | } else if (redline) { 57 | draw(coords[i]--coords[j], solid+red+linewidth(1.5)); 58 | } else { 59 | draw(coords[i]--coords[j], solid+linewidth(1.5)); 60 | } 61 | } 62 | } 63 | 64 | // Draw a directional indicator for how we specify points around a vertex. 65 | triple[] pts = {0.6*coords[vann] + 0.4*coords[cube_neighbors[vann][0]], 66 | 0.6*coords[vann] + 0.4*coords[cube_neighbors[vann][1]], 67 | 0.6*coords[vann] + 0.4*coords[cube_neighbors[vann][2]], 68 | 0.6*coords[vann] + 0.4*coords[cube_neighbors[vann][3]]}; 69 | draw(pts[0]..pts[1]..pts[2]..pts[3], Arrow3(arrow_size)); 70 | 71 | currentprojection=perspective( 72 | camera=(2.96554823701407,5.26167532052144,2.88816390168057), 73 | up=(-0.00156112565675572,-0.00286693866807605,0.00686845868317194), 74 | target=(0.527280331122584,0.531057161148581,0.359381656890007), 75 | zoom=0.872308215245426, 76 | angle=15.1450337404379, 77 | viewportshift=(-0.0161937341305386,0.0685370559208996), 78 | autoadjust=false); 79 | -------------------------------------------------------------------------------- /src/PYB11/Vertex3d.py: -------------------------------------------------------------------------------- 1 | from PYB11Generator import * 2 | 3 | @PYB11cppname("Vertex3d<>") 4 | class Vertex3d: 5 | '''The PolyClipper 3D (x,y,z) vertex type. 6 | 7 | Vertex3d is used to encode Polyhedra, so that PolyClipper Polyhedra are simply 8 | collections of vertices. A Vertex3d consists of a position and a vector of 9 | neighbor Vertex3d indices (a,b,c,...), which represent the connected vertices 10 | to this one going counterclockwise around this vertex viewed from outside the 11 | polyhedron. 12 | 13 | New vertices are created by clipping operations, and Vertex maintains a set of 14 | the plane IDs that have touched/clipped this vertex. 15 | 16 | Vertex3d also maintains a "comp" and "ID" attributes, which are primarily for 17 | internal usage during PolyClipper clipping operations. 18 | ''' 19 | 20 | PYB11typedefs = """ 21 | typedef Vector3d Vector; 22 | """ 23 | 24 | #--------------------------------------------------------------------------- 25 | # Constructors 26 | #--------------------------------------------------------------------------- 27 | def pyinit0(self): 28 | "Default constructor" 29 | 30 | def pyinit1(self, 31 | pos = "const Vector&"): 32 | "Construct with a position" 33 | 34 | def pyinit2(self, 35 | pos = "const Vector&", 36 | c = "const int"): 37 | "Construct with a position and initial 'comp' value" 38 | 39 | def pyinit3(self, 40 | rhs = "const Vertex3d<>&"): 41 | "Copy constructor" 42 | 43 | #--------------------------------------------------------------------------- 44 | # Operators 45 | #--------------------------------------------------------------------------- 46 | def __eq__(self): 47 | return 48 | 49 | #--------------------------------------------------------------------------- 50 | # Methods 51 | #--------------------------------------------------------------------------- 52 | @PYB11implementation(''' 53 | [](const Vertex3d<>& self) { 54 | auto result = "{pos=(" + std::to_string(self.position.x) + " " + std::to_string(self.position.y) + + " " + std::to_string(self.position.z) + 55 | "), neighbors=( "; 56 | for (const auto x: self.neighbors) result += std::to_string(x) + " "; 57 | result += "), ID=" + std::to_string(self.ID) + 58 | ", clips=( "; 59 | for (const auto x: self.clips) result += std::to_string(x) + " "; 60 | result += ")}"; 61 | return result; 62 | }''') 63 | def __repr__(self): 64 | return 65 | 66 | #--------------------------------------------------------------------------- 67 | # Attributes 68 | #--------------------------------------------------------------------------- 69 | position = PYB11readwrite() 70 | neighbors = PYB11readwrite() 71 | comp = PYB11readwrite() 72 | ID = PYB11readwrite() 73 | clips = PYB11readwrite() 74 | -------------------------------------------------------------------------------- /src/PYB11/Vector2d.py: -------------------------------------------------------------------------------- 1 | from PYB11Generator import * 2 | 3 | class Vector2d: 4 | "The PolyClipper (x,y) geometric vector type" 5 | 6 | #--------------------------------------------------------------------------- 7 | # Constructors 8 | #--------------------------------------------------------------------------- 9 | def pyinit0(self): 10 | "Default constructor" 11 | 12 | def pyinit1(self, 13 | X = "double", 14 | Y = "double"): 15 | "Construct with (X,Y) values" 16 | 17 | #--------------------------------------------------------------------------- 18 | # Operators 19 | #--------------------------------------------------------------------------- 20 | def __eq__(self): 21 | return 22 | 23 | def __imul__(self, rhs="double()"): 24 | return 25 | 26 | def __itruediv__(self, rhs="double()"): 27 | return 28 | 29 | def __iadd__(self): 30 | return 31 | 32 | def __isub__(self): 33 | return 34 | 35 | def __mul__(self, rhs="double()"): 36 | return 37 | 38 | def __rmul__(self, rhs="double()"): 39 | return 40 | 41 | def __truediv__(self, rhs="double()"): 42 | return 43 | 44 | def __add__(self): 45 | return 46 | 47 | def __sub__(self): 48 | return 49 | 50 | def __neg__(self): 51 | return 52 | 53 | @PYB11implementation("[](Vector2d &s) { return py::make_iterator(&s.x, &s.y+1u); }, py::keep_alive<0,1>()") 54 | def __iter__(self): 55 | "Python iteration through a Vector." 56 | 57 | #--------------------------------------------------------------------------- 58 | # Methods 59 | #--------------------------------------------------------------------------- 60 | @PYB11const 61 | def dot(self, rhs="const Vector2d&"): 62 | "Return the dot product" 63 | return "double" 64 | 65 | @PYB11const 66 | def crossmag(self, rhs="const Vector2d&"): 67 | "Return the z-value of the cross product as a scalar" 68 | return "double" 69 | 70 | @PYB11const 71 | def magnitude(self): 72 | "Return the magnitude of the vector" 73 | return "double" 74 | 75 | @PYB11const 76 | def magnitude2(self): 77 | "Return the square of the magnitude of the vector" 78 | return "double" 79 | 80 | @PYB11const 81 | def unitVector(self): 82 | "Return a unit vector in the same direction as this one" 83 | return "Vector2d" 84 | 85 | @PYB11implementation('''[](const Vector2d& self) { return "(" + std::to_string(self.x) + ", " + std::to_string(self.y) + ")"; }''') 86 | def __repr__(self): 87 | return 88 | 89 | #--------------------------------------------------------------------------- 90 | # Attributes 91 | #--------------------------------------------------------------------------- 92 | x = PYB11readwrite() 93 | y = PYB11readwrite() 94 | -------------------------------------------------------------------------------- /src/PYB11/Plane.py: -------------------------------------------------------------------------------- 1 | from PYB11Generator import * 2 | 3 | @PYB11template("VA") 4 | class Plane: 5 | """The PolyClipper plane. 6 | 7 | A plane is defined by a signed scalar distance (dist) and unit normal (normal). 8 | The distance d represents the signed shortest distance from the plane to the 9 | origin: 10 | 11 | dist = -P0.dot(normal), 12 | 13 | where "P0" is a point in the plane. Note the normal defines the "above" and 14 | "below" conventions for a plane, so for any point "p" with distance "s" from the 15 | plane: 16 | 17 | s = (p - P0).dot(normal) 18 | 19 | s < 0 implies p is below the plane, s > 0 above, and s == 0 means p is in the 20 | plane. 21 | 22 | Planes also optionally keep an integer ID, which is used during clipping 23 | operations to track which plane(s) are responsible for each vertex.""" 24 | 25 | PYB11typedefs = """ 26 | using Vector = typename %(VA)s::VECTOR; 27 | """ 28 | 29 | #--------------------------------------------------------------------------- 30 | # Constructors 31 | #--------------------------------------------------------------------------- 32 | def pyinit0(self): 33 | "Default constructor" 34 | 35 | def pyinit1(self, 36 | d = "const double", 37 | nhat = "const Vector"): 38 | "Construct with (signed distance, unit normal) = (d, nhat)" 39 | 40 | def pyinit2(self, 41 | p = "const Vector&", 42 | nhat = "const Vector&"): 43 | "Construct using a point in the plane (p) and unit normal (nhat)" 44 | 45 | def pyinit3(self, 46 | p = "const Vector&", 47 | nhat = "const Vector&", 48 | id = "const int"): 49 | "Construct using a point in the plane (p), unit normal (nhat), plane ID (id)" 50 | 51 | def pyinit4(self, 52 | rhs = "const Plane<%(VA)s>&"): 53 | "Copy constructor" 54 | 55 | #--------------------------------------------------------------------------- 56 | # Operators 57 | #--------------------------------------------------------------------------- 58 | def __eq__(self): 59 | return 60 | 61 | def __ne__(self): 62 | return 63 | 64 | def __lt__(self): 65 | return 66 | 67 | def __gt__(self): 68 | return 69 | 70 | #--------------------------------------------------------------------------- 71 | # Methods 72 | #--------------------------------------------------------------------------- 73 | @PYB11implementation('''[](const Plane<%(VA)s>& self) { return "{" + std::to_string(self.dist) + ", (" + std::to_string(self.normal.x) + ", " + std::to_string(self.normal.y) + ")}"; }''') 74 | def __repr__(self): 75 | return 76 | 77 | #--------------------------------------------------------------------------- 78 | # Attributes 79 | #--------------------------------------------------------------------------- 80 | dist = PYB11readwrite() 81 | normal = PYB11readwrite() 82 | -------------------------------------------------------------------------------- /src/PYB11/Vector3d.py: -------------------------------------------------------------------------------- 1 | from PYB11Generator import * 2 | 3 | class Vector3d: 4 | "The PolyClipper (x,y,z) geometric vector type" 5 | 6 | #--------------------------------------------------------------------------- 7 | # Constructors 8 | #--------------------------------------------------------------------------- 9 | def pyinit0(self): 10 | "Default constructor" 11 | 12 | def pyinit1(self, 13 | X = "double", 14 | Y = "double", 15 | Z = "double"): 16 | "Construct with (X,Y,Z) values" 17 | 18 | #--------------------------------------------------------------------------- 19 | # Operators 20 | #--------------------------------------------------------------------------- 21 | def __eq__(self): 22 | return 23 | 24 | def __imul__(self, rhs="double()"): 25 | return 26 | 27 | def __itruediv__(self, rhs="double()"): 28 | return 29 | 30 | def __iadd__(self): 31 | return 32 | 33 | def __isub__(self): 34 | return 35 | 36 | def __mul__(self, rhs="double()"): 37 | return 38 | 39 | def __rmul__(self, rhs="double()"): 40 | return 41 | 42 | def __truediv__(self, rhs="double()"): 43 | return 44 | 45 | def __add__(self): 46 | return 47 | 48 | def __sub__(self): 49 | return 50 | 51 | def __neg__(self): 52 | return 53 | 54 | @PYB11implementation("[](Vector3d &s) { return py::make_iterator(&s.x, &s.z+1u); }, py::keep_alive<0,1>()") 55 | def __iter__(self): 56 | "Python iteration through a Vector." 57 | 58 | #--------------------------------------------------------------------------- 59 | # Methods 60 | #--------------------------------------------------------------------------- 61 | @PYB11const 62 | def dot(self, rhs="const Vector3d&"): 63 | "Return the dot product" 64 | return "double" 65 | 66 | @PYB11const 67 | def cross(self, rhs="const Vector3d&"): 68 | "Return the cross product" 69 | return "Vector3d" 70 | 71 | @PYB11const 72 | def magnitude(self): 73 | "Return the magnitude of the vector" 74 | return "double" 75 | 76 | @PYB11const 77 | def magnitude2(self): 78 | "Return the square of the magnitude of the vector" 79 | return "double" 80 | 81 | @PYB11const 82 | def unitVector(self): 83 | "Return a unit vector in the same direction as this one" 84 | return "Vector3d" 85 | 86 | @PYB11implementation('''[](const Vector3d& self) { return "(" + std::to_string(self.x) + ", " + std::to_string(self.y) + ", " + std::to_string(self.z) + ")"; }''') 87 | def __repr__(self): 88 | return 89 | 90 | #--------------------------------------------------------------------------- 91 | # Attributes 92 | #--------------------------------------------------------------------------- 93 | x = PYB11readwrite() 94 | y = PYB11readwrite() 95 | z = PYB11readwrite() 96 | -------------------------------------------------------------------------------- /src/polyclipper_serialize.hh: -------------------------------------------------------------------------------- 1 | //---------------------------------PolyClipper--------------------------------// 2 | // Methods for serializing/dserializing PolyClipper types 3 | //----------------------------------------------------------------------------// 4 | #ifndef __PolyClipper_serialize__ 5 | #define __PolyClipper_serialize__ 6 | 7 | #include "polyclipper_utilities.hh" 8 | 9 | namespace PolyClipper { 10 | 11 | // Forward declarations 12 | template struct Vertex2d; 13 | template struct Vertex3d; 14 | 15 | namespace internal { 16 | 17 | // Serialize 18 | void serialize(const double val, std::string& buffer); 19 | void serialize(const int val, std::string& buffer); 20 | void serialize(const size_t val, std::string& buffer); 21 | void serialize(const std::string& val, std::string& buffer); 22 | template void serialize(const typename VA::VECTOR& val, std::string& buffer); 23 | template void serialize(const Vertex2d& val, std::string& buffer); 24 | template void serialize(const Vertex3d& val, std::string& buffer); 25 | template void serialize(const std::vector>& val, std::string& buffer); 26 | template void serialize(const std::vector>& val, std::string& buffer); 27 | template void serialize(const Plane& val, std::string& buffer); 28 | template void serialize(const std::vector>& val, std::string& buffer); 29 | 30 | // Deserialize 31 | void deserialize(double& val, std::string::const_iterator& itr, const std::string::const_iterator& endBuffer); 32 | void deserialize(int& val, std::string::const_iterator& itr, const std::string::const_iterator& endBuffer); 33 | void deserialize(size_t& val, std::string::const_iterator& itr, const std::string::const_iterator& endBuffer); 34 | void deserialize(std::string& val, std::string::const_iterator& itr, const std::string::const_iterator& endBuffer); 35 | template void deserialize(typename VA::VECTOR& val, std::string::const_iterator& itr, const std::string::const_iterator& endBuffer); 36 | template void deserialize(Vertex2d& val, std::string::const_iterator& itr, const std::string::const_iterator& endBuffer); 37 | template void deserialize(Vertex3d& val, std::string::const_iterator& itr, const std::string::const_iterator& endBuffer); 38 | template void deserialize(std::vector>& val, std::string::const_iterator& itr, const std::string::const_iterator& endBuffer); 39 | template void deserialize(std::vector>& val, std::string::const_iterator& itr, const std::string::const_iterator& endBuffer); 40 | template void deserialize(Plane& val, std::string::const_iterator& itr, const std::string::const_iterator& endBuffer); 41 | template void deserialize(std::vector>& val, std::string::const_iterator& itr, const std::string::const_iterator& endBuffer); 42 | 43 | } 44 | } 45 | 46 | #include "polyclipper_serializeImpl.hh" 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/polyclipper_adapter.hh: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // Trait class to use PolyClipper's native Vector types. 3 | // This is an example of the traits necessary to use your own Vector types 4 | // directly with PolyClipper. 5 | //------------------------------------------------------------------------------ 6 | #ifndef __PolyClipper_adapter__ 7 | #define __PolyClipper_adapter__ 8 | 9 | #include "polyclipper_utilities.hh" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace PolyClipper { 16 | namespace internal { 17 | 18 | template 19 | struct VectorAdapter { 20 | using VECTOR = VectorType; 21 | static VECTOR Vector(double a, double b) { return VECTOR(a, b); } // only 2D 22 | static VECTOR Vector(double a, double b, double c) { return VECTOR(a, b, c); } // only 3D 23 | static bool equal(const VECTOR& a, const VECTOR& b) { return a == b; } 24 | static double& x(VECTOR& a) { return a.x; } 25 | static double& y(VECTOR& a) { return a.y; } 26 | static double& z(VECTOR& a) { return a.z; } // only 3D 27 | static double x(const VECTOR& a) { return a.x; } 28 | static double y(const VECTOR& a) { return a.y; } 29 | static double z(const VECTOR& a) { return a.z; } // only 3D 30 | static double dot(const VECTOR& a, const VECTOR& b) { return a.dot(b); } 31 | static double crossmag(const VECTOR& a, const VECTOR& b) { return a.crossmag(b); } // only 2D 32 | static VECTOR cross(const VECTOR& a, const VECTOR& b) { return a.cross(b); } // only 3D 33 | static double magnitude2(const VECTOR& a) { return a.magnitude2(); } 34 | static double magnitude(const VECTOR& a) { return a.magnitude(); } 35 | static VECTOR& imul(VECTOR& a, const double b) { a *= b; return a; } 36 | static VECTOR& idiv(VECTOR& a, const double b) { a /= b; return a; } 37 | static VECTOR& iadd(VECTOR& a, const VECTOR& b) { a += b; return a; } 38 | static VECTOR& isub(VECTOR& a, const VECTOR& b) { a -= b; return a; } 39 | static VECTOR mul(const VECTOR& a, const double b) { return a * b; } 40 | static VECTOR div(const VECTOR& a, const double b) { return a / b; } 41 | static VECTOR add(const VECTOR& a, const VECTOR& b) { return a + b; } 42 | static VECTOR sub(const VECTOR& a, const VECTOR& b) { return a - b; } 43 | static VECTOR neg(const VECTOR& a) { return -a; } 44 | static VECTOR unitVector(const VECTOR& a) { return a.unitVector(); } 45 | static std::string str(const VECTOR& a) { std::ostringstream os; os << a; return os.str(); } // Pretty human readable representation 46 | static std::array get_triple(const VECTOR& a) { return a.triple(); } 47 | static void set_triple(VECTOR& a, 48 | const std::array& vals) { a.set_triple(vals); } 49 | }; 50 | 51 | } 52 | } 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeLists to build the PolyClipper library. 2 | cmake_minimum_required(VERSION 3.20) 3 | project(PolyClipper LANGUAGES CXX) 4 | 5 | #------------------------------------------------------------------------------- 6 | # Configure CMake 7 | #------------------------------------------------------------------------------- 8 | set(CMAKE_CXX_STANDARD 11) 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w") 10 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 11 | set(CMAKE_EXPORT_COMPILE_COMMANDS On) 12 | 13 | #------------------------------------------------------------------------------- 14 | # Options 15 | #------------------------------------------------------------------------------- 16 | option(POLYCLIPPER_ENABLE_PYTHON "Enable python interface" ON) 17 | option(POLYCLIPPER_MODULE_GEN "Generate the PolyClipper modules" ON) 18 | option(POLYCLIPPER_ENABLE_DOCS "enable the sphinx documentation" OFF) 19 | option(POLYCLIPPER_ENABLE_TESTS "enable the tests" ON) 20 | option(ENABLE_BOUNDCHECKING "enable Gnu compiler bound checking on memory" OFF) 21 | 22 | #------------------------------------------------------------------------------- 23 | # Install 24 | #------------------------------------------------------------------------------- 25 | if(NOT POLYCLIPPER_INSTALL_DIR) 26 | set(POLYCLIPPER_INSTALL_DIR include) 27 | endif() 28 | message("-- PolyClipper install path: ${CMAKE_INSTALL_PREFIX}/${POLYCLIPPER_INSTALL_DIR}") 29 | 30 | #------------------------------------------------------------------------------- 31 | # Include standard build system logic and options / definitions 32 | #------------------------------------------------------------------------------- 33 | if (${ENABLE_BOUNDCHECKING}) 34 | add_definitions(-D_GLIBCXX_DEBUG=1) 35 | endif() 36 | 37 | #------------------------------------------------------------------------------- 38 | # Are we building the python package? 39 | # PolyClipper doesn't actually build any compiled objects in the C++ only case, 40 | # but you can still install the C++ headers. 41 | #------------------------------------------------------------------------------- 42 | if (POLYCLIPPER_ENABLE_PYTHON) 43 | 44 | if (NOT PYTHON_EXE) 45 | find_package(Python3 COMPONENTS Interpreter Development) 46 | set(PYTHON_EXE "${Python3_EXECUTABLE}") 47 | message("-- PYTHON_EXE: ${PYTHON_EXE}") 48 | endif() 49 | 50 | # We need where to find PYB11Generator 51 | if (NOT PYB11GENERATOR_ROOT_DIR) 52 | set(PYB11GENERATOR_ROOT_DIR "${CMAKE_SOURCE_DIR}/extern/PYB11Generator") 53 | message("-- PYB11GENERATOR_ROOT_DIR: ${PYB11GENERATOR_ROOT_DIR}") 54 | endif() 55 | # Previous variable for PYB11 directory path 56 | set(PYB11GEN_PATH ${PYB11GENERATOR_ROOT_DIR}) 57 | include(${PYB11GENERATOR_ROOT_DIR}/cmake/PYB11Generator.cmake) 58 | 59 | # Install python module path 60 | if (NOT POLYCLIPPER_PYTHON_INSTALL) 61 | #set(POLYCLIPPER_PYTHON_INSTALL ${CMAKE_INSTALL_PREFIX}/lib/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages/PolyClipper) 62 | set(POLYCLIPPER_PYTHON_INSTALL ${CMAKE_INSTALL_PREFIX}/lib/python/site-packages/PolyClipper) 63 | endif() 64 | message("-- Python module install path ${POLYCLIPPER_PYTHON_INSTALL}") 65 | else() 66 | set(POLYCLIPPER_MODULE_GEN OFF) 67 | endif() 68 | 69 | set(POLYCLIPPER_PYTHON_DEPENDS ) 70 | 71 | #------------------------------------------------------------------------------- 72 | # Prepare to build the src 73 | #------------------------------------------------------------------------------- 74 | add_subdirectory(src) 75 | 76 | #------------------------------------------------------------------------------- 77 | # Add the documentation 78 | #------------------------------------------------------------------------------- 79 | if (${POLYCLIPPER_ENABLE_DOCS}) 80 | add_subdirectory(docs) 81 | endif() 82 | 83 | #------------------------------------------------------------------------------- 84 | # Add any tests 85 | #------------------------------------------------------------------------------- 86 | if (${POLYCLIPPER_ENABLE_TESTS}) 87 | add_subdirectory(test/test_array_vector) 88 | endif() 89 | 90 | -------------------------------------------------------------------------------- /test/test_array_vector/test_array_vector_2d.cc: -------------------------------------------------------------------------------- 1 | #include "polyclipper2d.hh" 2 | 3 | #include 4 | #include 5 | 6 | // Define a trait class for using a simple C style array of doubles as 2D Vector type for use in PolyClipper. 7 | struct ArrayAdapter2d { 8 | using VECTOR = std::array; 9 | static VECTOR Vector(double a, double b) { return {a, b}; } // only 2D 10 | static bool equal(const VECTOR& a, const VECTOR& b) { return (a[0] == b[0]) and (a[1] == b[1]); } 11 | static double& x(VECTOR& a) { return a[0]; } 12 | static double& y(VECTOR& a) { return a[1]; } 13 | static double x(const VECTOR& a) { return a[0]; } 14 | static double y(const VECTOR& a) { return a[1]; } 15 | static double dot(const VECTOR& a, const VECTOR& b) { return a[0]*b[0] + a[1]*b[1]; } 16 | static double crossmag(const VECTOR& a, const VECTOR& b) { return a[0]*b[1] - a[1]*b[0]; } // only 2D 17 | static double magnitude2(const VECTOR& a) { return a[0]*a[0] + a[1]*a[1]; } 18 | static double magnitude(const VECTOR& a) { return std::sqrt(magnitude2(a)); } 19 | static VECTOR& imul(VECTOR& a, const double b) { a[0] *= b; a[1] *= b; return a; } 20 | static VECTOR& idiv(VECTOR& a, const double b) { a[0] /= b; a[1] /= b; return a; } 21 | static VECTOR& iadd(VECTOR& a, const VECTOR& b) { a[0] += b[0]; a[1] += b[1]; return a; } 22 | static VECTOR& isub(VECTOR& a, const VECTOR& b) { a[0] -= b[0]; a[1] -= b[1]; return a; } 23 | static VECTOR mul(const VECTOR& a, const double b) { return Vector(a[0] * b, a[1] * b); } 24 | static VECTOR div(const VECTOR& a, const double b) { return Vector(a[0] / b, a[1] / b); } 25 | static VECTOR add(const VECTOR& a, const VECTOR& b) { return Vector(a[0] + b[0], a[1] + b[1]); } 26 | static VECTOR sub(const VECTOR& a, const VECTOR& b) { return Vector(a[0] - b[0], a[1] - b[1]); } 27 | static VECTOR neg(const VECTOR& a) { return Vector(-a[0], -a[1]); } 28 | static VECTOR unitVector(const VECTOR& a) { auto mag = magnitude(a); return mag > 1.0e-15 ? div(a, mag) : Vector(1.0, 0.0); } 29 | static std::string str(const VECTOR& a) { std::ostringstream os; os << "(" << a[0] << " " << a[1] << ")"; return os.str(); } 30 | static std::array get_triple(const VECTOR& a) { return {a[0], a[1], 0.0}; } 31 | static void set_triple(VECTOR& a, 32 | const std::array vals) { a = {vals[0], vals[1]}; } 33 | }; 34 | 35 | int main() { 36 | 37 | using VA = ArrayAdapter2d; 38 | using Vector = VA::VECTOR; 39 | using Polygon = std::vector>; 40 | using Plane = PolyClipper::Plane; 41 | 42 | // Make a square 43 | // 3 2 44 | // |----| 45 | // | | 46 | // |----| 47 | // 0 1 48 | const std::vector square_points = {VA::Vector(0.0,0.0), VA::Vector(10.0,0.0), VA::Vector(10.0,10.0), VA::Vector(0.0,10.0)}; 49 | const std::vector> square_neighbors = {{3, 1}, {0, 2}, {1, 3}, {2, 0}}; 50 | Polygon poly; 51 | double area; 52 | Vector cent; 53 | PolyClipper::initializePolygon(poly, square_points, square_neighbors); 54 | PolyClipper::moments(area, cent, poly); 55 | std::cout << "Initial polygon: " << polygon2string(poly) << std::endl 56 | << "Moments: " << area << " " << VA::str(cent) << std::endl << std::endl; 57 | 58 | // Clip by a couple of planes. 59 | const std::vector planes = {Plane(VA::Vector(0.2, 0.2), VA::unitVector(VA::Vector(1.0, 1.0)), 10), 60 | Plane(VA::Vector(0.2, 0.8), VA::unitVector(VA::Vector(0.5, -1.0)), 20)}; 61 | PolyClipper::clipPolygon(poly, planes); 62 | PolyClipper::moments(area, cent, poly); 63 | std::cout << "After clipping: " << polygon2string(poly) << std::endl 64 | << "Moments: " << area << " " << VA::str(cent) << std::endl; 65 | 66 | return 0; 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /docs/polygon_methods.rst: -------------------------------------------------------------------------------- 1 | ######################################## 2 | Functions for manipulating polygons 3 | ######################################## 4 | 5 | These are the methods provided in the ``PolyClipper`` namespace (C++) and/or ``PolyClipper`` module (Python) for manipulating polygons. Recall that a polygon is simply a ``std::vector>``, as described in :ref:`PolyClipper concepts`. All methods here are included in the single header file ``polyclipper2d.hh``. 6 | 7 | .. cpp:namespace:: PolyClipper 8 | 9 | .. cpp:function:: template> \ 10 | void initializePolygon(std::vector>& poly, const std::vector& positions, const std::vector>& neighbors) 11 | 12 | Initialize a polygon by constructing the necessary ``PolyClipper::Vertex2d`` objects described by the ``positions`` and ``neighbors``. Note that the length of these arrays should be identical (i.e., the number of vertices in the resulting polygon). Each element of the ``neighbors`` array should be 2 elements long, listing the (clockwise, counterclockwise) neighbors for the vertex at the corresponding index in the ``positions`` array. 13 | 14 | See the examples in :ref:`PolyClipper concepts`. 15 | 16 | .. cpp:function:: template> \ 17 | std::string polygon2string(const std::vector>& poly) 18 | 19 | Creates a human-readable string representation of the polygon. 20 | 21 | .. cpp:function:: template> \ 22 | void moments(double& zerothMoment, typename VA::VECTOR& firstMoment, \ 23 | const std::vector>& polygon) 24 | 25 | Compute the zeroth (area) and first (centroid) moment of a polygon. 26 | 27 | .. note:: 28 | In Python this method has a different signature, as the moments are returned as a Python tuple: 29 | 30 | .. py:function:: moments(polygon) -> (double, Vector2d) 31 | 32 | .. warning:: 33 | While the area returned in this function is always correct, the centroid is only correct for convex polygons. This should be generalized to work for all polygons in a future release. 34 | 35 | .. cpp:function:: template> \ 36 | void clipPolygon(std::vector>& poly, \ 37 | const std::vector>& planes) 38 | 39 | Clip a polygon by a set of planes in place. Examples are shown in :ref:`Clipping operations`. The region of the polygon above the each plane (in the direction of the plane normal) is retained. 40 | 41 | After clipping, the ``Vertex2d::ID`` and ``Vertex2d::clips`` attribute of the vertices in the polygon are modified, such that ID holds a unique identifier for each remaining vertex, and clips holds the ID's of any planes used to create the vertex. 42 | 43 | .. cpp:function:: template> \ 44 | void collapseDegenerates(std::vector>& poly, \ 45 | const double tol) 46 | 47 | Remove redundant vertices in the polygon in place, such that any edge of the input polygon with length less than ``tol`` is removed and their vertices combined. 48 | 49 | .. cpp:function:: template> \ 50 | std::vector> extractFaces(const std::vector>& poly) 51 | 52 | Return an array of vertex indices that represent the faces (or edges) of the polygon. The length of the returned array is the number of faces, and each element is of length 2 representing the face/edge. 53 | 54 | .. cpp:function:: template> \ 55 | std::vector> commonFaceClips(const std::vector>& poly, \ 56 | const std::vector>& faces) 57 | 58 | Compute the unique plane ID's responsible for creating each face in the polygon. Assumes the ``Vertex2d::clips`` attribute has been filled in by clipping the polygon. 59 | 60 | .. cpp:function:: template> \ 61 | std::vector> splitIntoTriangles(const std::vector>& poly, \ 62 | const double tol = 0.0) 63 | 64 | Return a triangulation of the polygon. The result is an array of triples, with each triple the indices of the vertices making up each triangle. The ``tol`` attribute is used to reject any triangles with areas less than ``tol``. 65 | 66 | .. warning:: 67 | This method currently only works for convex polygons, and raises an assertion if called with a non-convex polygon. 68 | -------------------------------------------------------------------------------- /docs/building.rst: -------------------------------------------------------------------------------- 1 | ######################################################### 2 | Obtaining, building, installing, and testing PolyClipper 3 | ######################################################### 4 | 5 | PolyClipper minimally requires a C++11 compliant compiler. In order to build the Python interface you also need a Python (version 3.8 or later) installation. If you use git to clone the PolyClipper source note PolyClipper includes three submodules: `BLT `_, `pybind11 `_, and `PYB11Generator `_. In order to ensure these are properly downloaded when cloning PolyClipper be sure to use the ``--recurse-submodules`` git option:: 6 | 7 | git clone --recursive https://github.com/LLNL/PolyClipper.git 8 | 9 | If you forget to use the ``--recursive`` argument or if you checkout from a different branch you should execute:: 10 | 11 | git submodule update --init --recursive 12 | 13 | The C++ installation of PolyClipper is header only: to use from your own C++ code you simply need to ``#include `` or ``#include ``, as appropriate. The Python install consists of a single compiled library, ``PolyClipper``. 14 | 15 | ---------- 16 | Building 17 | ---------- 18 | 19 | PolyClipper uses the `CMake `_ build system for configuration, followed by whatever native build is available on your own platform as appropriately generated by CMake. In this example we'll consider a Unix makefile style installation. 20 | 21 | .. note:: 22 | For C++ PolyClipper is a header only library, so if you just want to try it out without "building" and installing from C++, you can simply point your C++ include paths wherever you download and put PolyClippers header files (from the ``src`` directory in PolyClipper). 23 | 24 | Generally building PolyClipper on a Unix-like system is as simple as:: 25 | 26 | cd 27 | mkdir build 28 | cd build 29 | cmake -DCMAKE_INSTALL_PREFIX= .. 30 | make 31 | make install 32 | 33 | If you also want these documentation files to be built you need to specify a Sphinx executable, and optionally a theme if you don't have/want the ``readthedocs`` Sphinx theme. Useful CMake options that can be specified on the configuration line include: 34 | 35 | CMake variables 36 | -------------------- 37 | 38 | CMAKE_BUILD_TYPE : (Debug, Release, RelWithDebInfo, MinSizeRel) 39 | Choose the type of build -- for more information see the `CMake documentation `_. 40 | 41 | CMAKE_INSTALL_PREFIX 42 | The top-level path for installing PolyClipper include files, libraries, and any Python modules or documentation. 43 | 44 | POLYCLIPPER_PYTHON_INSTALL 45 | Optionally specify a path for installing the PolyClipper Python module. If not specified the Python module is installed to ``${CMAKE_INSTALL_PREFIX}/lib/python/site-packages/PolyClipper``. 46 | 47 | PYTHON_EXE 48 | Optionally specify a specific Python executable to use when building Python module. If this is not given PolyClipper checks to see if a suitable Python can be found and used automatically. 49 | 50 | LOOKUP_PYBIND11_INCLUDE_PATH 51 | Optionally force the PYTHON_EXE to try and import pybind11 and deduce its include path. If this parameter is not specified the pybind11 downloaded with PolyClipper is used. 52 | 53 | PYBIND11_INCLUDE_PATH 54 | If desired you can explicitly specify the path to a pybind11 installation. By default the PolyClipper downloaded version is used. 55 | 56 | PYB11GENERATOR_ROOT_DIR 57 | Similarly you can explicitly specify a path to a PYB11Generator installation. By default the PolyClipper downloaded version is used. 58 | 59 | POLYCLIPPER_ENABLE_DOCS 60 | Turn Sphinx documentation generation on/off. 61 | 62 | SPHINX_EXECUTABLE 63 | Specify where the Sphinx executable is that should be used to build documentation. If not given, the executable path is searched for ``sphinx-build``. 64 | 65 | SPHINX_THEME 66 | Give the Sphinx theme to use when generating documentation -- defaults to ``default``. 67 | 68 | SPHINX_THEME_DIR 69 | Where to look for Sphinx themes. 70 | 71 | POLYCLIPPER_MODULE_GEN 72 | Generate the PolyClipper python modules -- defaults to ``ON``. 73 | 74 | POLYCLIPPER_ENABLE_PYTHON 75 | Install python interface, disables ``POLYCLIPPER_MODULE_GEN`` if turned off -- defaults to ``ON``. 76 | 77 | POLYCLIPPER_INSTALL_DIR 78 | Specify where the header files will be installed -- defaults to ``include``. 79 | 80 | ------- 81 | Testing 82 | ------- 83 | 84 | PolyClipper can be tested using the Python module (if built). There are two unit test classes in the ``test`` directory: ``testPolyClipper2d.py`` and ``testPolyClipper3d.py``. So, once you have added the location of the ``PolyClipper.so`` file to your PYTHONPATH, testing is as simple as executing:: 85 | 86 | cd test 87 | python testPolyClipper2d.py -v 88 | python testPolyClipper3d.py -v 89 | 90 | -------------------------------------------------------------------------------- /docs/polyhedron_methods.rst: -------------------------------------------------------------------------------- 1 | ######################################## 2 | Functions for manipulating polyhedra 3 | ######################################## 4 | 5 | These are the methods provided in the ``PolyClipper`` namespace (C++) and/or ``PolyClipper`` module (Python) for manipulating polyhedra. Recall that a polyhedron is simply a ``std::vector``, as described in :ref:`PolyClipper concepts`. All methods here are included in the single header file ``polyclipper3d.hh``. 6 | 7 | .. cpp:namespace:: PolyClipper 8 | 9 | .. cpp:function:: template> \ 10 | std::vector> splitIntoTriangles(const std::vector>& poly, \ 11 | const double tol = 0.0) 12 | 13 | Initialize a polyhedron by constructing the necessary ``PolyClipper::Vertex3d`` objects described by the ``positions`` and ``neighbors``. Note that the length of these arrays should be identical (i.e., the number of vertices in the resulting polyhedron). Each element of the ``neighbors`` array is an array of the neighbors for the vertex at the corresponding index in the ``positions`` array, where these neighbors are listed in counter-clockwise order around the vertex as viewed from the exterior of the polyhedron. 14 | 15 | See the examples in :ref:`PolyClipper concepts`. 16 | 17 | .. cpp:function:: template> \ 18 | std::string polyhedron2string(const std::vector>& poly) 19 | 20 | Creates a human-readable string representation of the polyhedron. 21 | 22 | .. cpp:function:: template> \ 23 | void moments(double& zerothMoment, typename VA::VECTOR& firstMoment, \ 24 | const std::vector>& polyhedron) 25 | 26 | Compute the zeroth (volume) and first (centroid) moment of a polyhedron. 27 | 28 | .. note:: 29 | In Python this method has a different signature, as the moments are returned as a Python tuple: 30 | 31 | .. py:function:: moments(polyhedron) -> (double, Vector3d) 32 | 33 | .. warning:: 34 | While the volume returned in this function is always correct, the centroid is only correct for convex polyhedra. This should be generalized to work for all polyhedra in a future release. 35 | 36 | .. cpp:function:: template> \ 37 | void clipPolyhedron(std::vector>& poly, \ 38 | const std::vector>& planes) 39 | 40 | Clip a polyhedron by a set of planes in place. Examples are shown in :ref:`Clipping operations`. The region of the polyhedron above the each plane (in the direction of the plane normal) is retained. 41 | 42 | After clipping, the ``Vertex3d::ID`` and ``Vertex3d::clips`` attribute of the vertices in the polyhedron are modified, such that ID holds a unique identifier for each remaining vertex, and clips holds the ID's of any planes used to create the vertex. 43 | 44 | .. cpp:function:: template> \ 45 | void collapseDegenerates(std::vector>& poly, \ 46 | const double tol) 47 | 48 | Remove redundant vertices in the polyhedron in place, such that any edge of the input polyhedron with length less than ``tol`` is removed and their vertices combined. It is possible entire faces of the polyhedron may be removed in this process, though only edge lengths are examined. 49 | 50 | .. cpp:function:: template> \ 51 | std::vector> extractFaces(const std::vector>& poly) 52 | 53 | Return an array of the vertex indices that represent the faces of the polyhedron. The length of the returned array is the number of faces, while each element is the set of vertices (3 or more) in counter-clockwise order (viewed from the exterior of the polyhedron). 54 | 55 | .. cpp:function:: template> \ 56 | std::vector> commonFaceClips(const std::vector>& poly, \ 57 | const std::vector>& faces) 58 | 59 | Compute the unique plane ID's responsible for creating each face in the polyhedron. Assumes the ``Vertex3d::clips`` attribute has been filled in by clipping the polyhedron. 60 | 61 | .. cpp:function:: template> \ 62 | std::vector> splitIntoTetrahedra(const std::vector>& poly, \ 63 | const double tol = 0.0) 64 | 65 | Return a tetrahedralization of the polyhedron. The result is an array of quartets, with each quartet the indices of the vertices making up each tetrahedron. The ``tol`` attribute is used to reject any tetrahedron with volumes less than ``tol``. 66 | 67 | .. warning:: 68 | This method currently only works for convex polyhedra, and raises an assertion if called with a non-convex polyhedron. 69 | -------------------------------------------------------------------------------- /test/test_array_vector/test_array_vector_3d.cc: -------------------------------------------------------------------------------- 1 | #include "polyclipper3d.hh" 2 | 3 | #include 4 | #include 5 | 6 | // Define a trait class for using a simple C style array of doubles as 3D Vector type for use in PolyClipper. 7 | struct ArrayAdapter3d { 8 | using VECTOR = std::array; 9 | static VECTOR Vector(double a, double b, double c) { return {a, b, c}; } // only 3D 10 | static bool equal(const VECTOR& a, const VECTOR& b) { return (a[0] == b[0]) and (a[1] == b[1]) and (a[2] == b[2]); } 11 | static double& x(VECTOR& a) { return a[0]; } 12 | static double& y(VECTOR& a) { return a[1]; } 13 | static double& z(VECTOR& a) { return a[2]; } 14 | static double x(const VECTOR& a) { return a[0]; } 15 | static double y(const VECTOR& a) { return a[1]; } 16 | static double z(const VECTOR& a) { return a[2]; } 17 | static double dot(const VECTOR& a, const VECTOR& b) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; } 18 | static VECTOR cross(const VECTOR& a, const VECTOR& b) { return {a[1]*b[2] - a[2]*b[1], // only 3D 19 | a[2]*b[0] - a[0]*b[2], 20 | a[0]*b[1] - a[1]*b[0]}; } 21 | static double magnitude2(const VECTOR& a) { return a[0]*a[0] + a[1]*a[1] + a[2]*a[2]; } 22 | static double magnitude(const VECTOR& a) { return std::sqrt(magnitude2(a)); } 23 | static VECTOR& imul(VECTOR& a, const double b) { a[0] *= b; a[1] *= b; a[2] *= b; return a; } 24 | static VECTOR& idiv(VECTOR& a, const double b) { a[0] /= b; a[1] /= b; a[2] /= b; return a; } 25 | static VECTOR& iadd(VECTOR& a, const VECTOR& b) { a[0] += b[0]; a[1] += b[1]; a[2] += b[2]; return a; } 26 | static VECTOR& isub(VECTOR& a, const VECTOR& b) { a[0] -= b[0]; a[1] -= b[1]; a[2] -= b[2]; return a; } 27 | static VECTOR mul(const VECTOR& a, const double b) { return Vector(a[0] * b, a[1] * b, a[2] * b); } 28 | static VECTOR div(const VECTOR& a, const double b) { return Vector(a[0] / b, a[1] / b, a[2] / b); } 29 | static VECTOR add(const VECTOR& a, const VECTOR& b) { return Vector(a[0] + b[0], a[1] + b[1], a[2] + b[2]); } 30 | static VECTOR sub(const VECTOR& a, const VECTOR& b) { return Vector(a[0] - b[0], a[1] - b[1], a[2] - b[2]); } 31 | static VECTOR neg(const VECTOR& a) { return Vector(-a[0], -a[1], -a[2]); } 32 | static VECTOR unitVector(const VECTOR& a) { auto mag = magnitude(a); return mag > 1.0e-15 ? div(a, mag) : Vector(1.0, 0.0, 0.0); } 33 | static std::string str(const VECTOR& a) { std::ostringstream os; os << "(" << a[0] << " " << a[1] << " " << a[2] << ")"; return os.str(); } 34 | static std::array get_triple(const VECTOR& a) { return a; } 35 | static void set_triple(VECTOR& a, 36 | const std::array vals) { a = vals; } 37 | }; 38 | 39 | int main() { 40 | 41 | using VA = ArrayAdapter3d; 42 | using Vector = VA::VECTOR; 43 | using Polyhedron = std::vector>; 44 | using Plane = PolyClipper::Plane; 45 | 46 | // Make a cube |y 47 | // | 48 | // |____x 49 | // 3/-----/2 / 50 | // / /| /z 51 | // 7|-----|6| 52 | // | | | 53 | // | 0 | /1 54 | // |_____|/ 55 | // 4 5 56 | // 57 | const std::vector cube_points = {VA::Vector(0,0,0), VA::Vector(10,0,0), VA::Vector(10,10,0), VA::Vector(0,10,0), 58 | VA::Vector(0,0,10), VA::Vector(10,0,10), VA::Vector(10,10,10), VA::Vector(0,10,10)}; 59 | const std::vector> cube_neighbors = {{1, 4, 3}, 60 | {5, 0, 2}, 61 | {3, 6, 1}, 62 | {7, 2, 0}, 63 | {5, 7, 0}, 64 | {1, 6, 4}, 65 | {5, 2, 7}, 66 | {4, 6, 3}}; 67 | const std::vector> cube_facets = {{4, 5, 6, 7}, 68 | {1, 2, 6, 5}, 69 | {0, 3, 2, 1}, 70 | {4, 7, 3, 0}, 71 | {6, 2, 3, 7}, 72 | {1, 5, 4, 0}}; 73 | Polyhedron poly; 74 | double vol; 75 | Vector cent; 76 | PolyClipper::initializePolyhedron(poly, cube_points, cube_neighbors); 77 | PolyClipper::moments(vol, cent, poly); 78 | std::cout << "Initial polyhedron: " << polyhedron2string(poly) << std::endl 79 | << "Moments: " << vol << " " << VA::str(cent) << std::endl << std::endl; 80 | 81 | // Clip by a couple of planes. 82 | const std::vector planes = {Plane(VA::Vector(0.2, 0.2, 0.2), VA::unitVector(VA::Vector(1.0, 1.0, 1.0)), 10), 83 | Plane(VA::Vector(0.2, 0.8, 0.8), VA::unitVector(VA::Vector(0.5, -1.0, -1.0)), 20)}; 84 | PolyClipper::clipPolyhedron(poly, planes); 85 | PolyClipper::moments(vol, cent, poly); 86 | std::cout << "After clipping: " << polyhedron2string(poly) << std::endl 87 | << "Moments: " << vol << " " << VA::str(cent) << std::endl; 88 | 89 | return 0; 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/conf.py.in: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = u'PolyClipper' 23 | copyright = u'2020, LLNS' 24 | author = u'J. Michael Owen' 25 | 26 | # The short X.Y version 27 | version = u'1.2' 28 | # The full version, including alpha/beta/rc tags 29 | release = u'1.2' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.autodoc', 43 | #'sphinx.ext.imgmath', 44 | 'sphinx.ext.mathjax', 45 | 'sphinx.ext.githubpages', 46 | 'sphinx.ext.autosectionlabel', 47 | ] 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ['_templates'] 51 | 52 | # The suffix(es) of source filenames. 53 | # You can specify multiple suffix as a list of string: 54 | # 55 | # source_suffix = ['.rst', '.md'] 56 | source_suffix = '.rst' 57 | 58 | # The master toctree document. 59 | master_doc = 'index' 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | language = None 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | # This pattern also affects html_static_path and html_extra_path. 71 | exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store'] 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | pygments_style = None 75 | 76 | 77 | # -- Options for HTML output ------------------------------------------------- 78 | 79 | # The theme to use for HTML and HTML Help pages. See the documentation for 80 | # a list of builtin themes. 81 | # 82 | html_theme = '@SPHINX_THEME@' # 'alabaster' 83 | 84 | # Theme options are theme-specific and customize the look and feel of a theme 85 | # further. For a list of options available for each theme, see the 86 | # documentation. 87 | # 88 | # html_theme_options = {} 89 | 90 | # Add any paths that contain custom static files (such as style sheets) here, 91 | # relative to this directory. They are copied after the builtin static files, 92 | # so a file named "default.css" will overwrite the builtin "default.css". 93 | html_static_path = ['@SPHINX_THEME_DIR@'] # ['_static'] 94 | 95 | # Custom sidebar templates, must be a dictionary that maps document names 96 | # to template names. 97 | # 98 | # The default sidebars (for documents that don't match any pattern) are 99 | # defined by theme itself. Builtin themes are using these templates by 100 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 101 | # 'searchbox.html']``. 102 | # 103 | # html_sidebars = {} 104 | 105 | 106 | # -- Options for HTMLHelp output --------------------------------------------- 107 | 108 | # Output file base name for HTML help builder. 109 | htmlhelp_basename = 'PolyClipperdoc' 110 | 111 | 112 | # -- Options for LaTeX output ------------------------------------------------ 113 | 114 | latex_elements = { 115 | # The paper size ('letterpaper' or 'a4paper'). 116 | # 117 | # 'papersize': 'letterpaper', 118 | 119 | # The font size ('10pt', '11pt' or '12pt'). 120 | # 121 | # 'pointsize': '10pt', 122 | 123 | # Additional stuff for the LaTeX preamble. 124 | # 125 | # 'preamble': '', 126 | 127 | # Latex figure (float) alignment 128 | # 129 | # 'figure_align': 'htbp', 130 | } 131 | 132 | # Grouping the document tree into LaTeX files. List of tuples 133 | # (source start file, target name, title, 134 | # author, documentclass [howto, manual, or own class]). 135 | latex_documents = [ 136 | (master_doc, 'PolyClipper.tex', u'PolyClipper Documentation', 137 | u'J. Michael Owen', 'manual'), 138 | ] 139 | 140 | 141 | # -- Options for manual page output ------------------------------------------ 142 | 143 | # One entry per manual page. List of tuples 144 | # (source start file, name, description, authors, manual section). 145 | man_pages = [ 146 | (master_doc, 'polyclipper', u'PolyClipper Documentation', 147 | [author], 1) 148 | ] 149 | 150 | 151 | # -- Options for Texinfo output ---------------------------------------------- 152 | 153 | # Grouping the document tree into Texinfo files. List of tuples 154 | # (source start file, target name, title, author, 155 | # dir menu entry, description, category) 156 | texinfo_documents = [ 157 | (master_doc, 'PolyClipper', u'PolyClipper Documentation', 158 | author, 'J. Michael Owen', 'One line description of project.', 159 | 'Miscellaneous'), 160 | ] 161 | 162 | 163 | # -- Options for Epub output ------------------------------------------------- 164 | 165 | # Bibliographic Dublin Core info. 166 | epub_title = project 167 | 168 | # The unique identifier of the text. This can be a ISBN number 169 | # or the project homepage. 170 | # 171 | # epub_identifier = '' 172 | 173 | # A unique identification for the text. 174 | # 175 | # epub_uid = '' 176 | 177 | # A list of files that should not be packed into the epub file. 178 | epub_exclude_files = ['search.html'] 179 | 180 | 181 | # -- Extension configuration ------------------------------------------------- 182 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | import sphinx_rtd_theme 20 | 21 | 22 | # -- Project information ----------------------------------------------------- 23 | 24 | project = u'PolyClipper' 25 | copyright = u'2020, LLNS' 26 | author = u'J. Michael Owen' 27 | 28 | # The short X.Y version 29 | version = u'1.2' 30 | # The full version, including alpha/beta/rc tags 31 | release = u'1.2' 32 | 33 | 34 | # -- General configuration --------------------------------------------------- 35 | 36 | # If your documentation needs a minimal Sphinx version, state it here. 37 | # 38 | # needs_sphinx = '1.0' 39 | 40 | # Add any Sphinx extension module names here, as strings. They can be 41 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 42 | # ones. 43 | extensions = [ 44 | 'sphinx.ext.autodoc', 45 | #'sphinx.ext.imgmath', 46 | 'sphinx.ext.mathjax', 47 | 'sphinx.ext.githubpages', 48 | 'sphinx.ext.autosectionlabel', 49 | ] 50 | 51 | # Add any paths that contain templates here, relative to this directory. 52 | templates_path = ['_templates'] 53 | 54 | # The suffix(es) of source filenames. 55 | # You can specify multiple suffix as a list of string: 56 | # 57 | # source_suffix = ['.rst', '.md'] 58 | source_suffix = '.rst' 59 | 60 | # The master toctree document. 61 | master_doc = 'index' 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This pattern also affects html_static_path and html_extra_path. 73 | exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store'] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = None 77 | 78 | 79 | # -- Options for HTML output ------------------------------------------------- 80 | 81 | # The theme to use for HTML and HTML Help pages. See the documentation for 82 | # a list of builtin themes. 83 | # 84 | html_theme = 'sphinx_rtd_theme' # 'alabaster' 85 | 86 | # Theme options are theme-specific and customize the look and feel of a theme 87 | # further. For a list of options available for each theme, see the 88 | # documentation. 89 | # 90 | # html_theme_options = {} 91 | 92 | # Add any paths that contain custom static files (such as style sheets) here, 93 | # relative to this directory. They are copied after the builtin static files, 94 | # so a file named "default.css" will overwrite the builtin "default.css". 95 | html_static_path = [''] # ['_static'] 96 | 97 | # Custom sidebar templates, must be a dictionary that maps document names 98 | # to template names. 99 | # 100 | # The default sidebars (for documents that don't match any pattern) are 101 | # defined by theme itself. Builtin themes are using these templates by 102 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 103 | # 'searchbox.html']``. 104 | # 105 | # html_sidebars = {} 106 | 107 | 108 | # -- Options for HTMLHelp output --------------------------------------------- 109 | 110 | # Output file base name for HTML help builder. 111 | htmlhelp_basename = 'PolyClipperdoc' 112 | 113 | 114 | # -- Options for LaTeX output ------------------------------------------------ 115 | 116 | latex_elements = { 117 | # The paper size ('letterpaper' or 'a4paper'). 118 | # 119 | # 'papersize': 'letterpaper', 120 | 121 | # The font size ('10pt', '11pt' or '12pt'). 122 | # 123 | # 'pointsize': '10pt', 124 | 125 | # Additional stuff for the LaTeX preamble. 126 | # 127 | # 'preamble': '', 128 | 129 | # Latex figure (float) alignment 130 | # 131 | # 'figure_align': 'htbp', 132 | } 133 | 134 | # Grouping the document tree into LaTeX files. List of tuples 135 | # (source start file, target name, title, 136 | # author, documentclass [howto, manual, or own class]). 137 | latex_documents = [ 138 | (master_doc, 'PolyClipper.tex', u'PolyClipper Documentation', 139 | u'J. Michael Owen', 'manual'), 140 | ] 141 | 142 | 143 | # -- Options for manual page output ------------------------------------------ 144 | 145 | # One entry per manual page. List of tuples 146 | # (source start file, name, description, authors, manual section). 147 | man_pages = [ 148 | (master_doc, 'polyclipper', u'PolyClipper Documentation', 149 | [author], 1) 150 | ] 151 | 152 | 153 | # -- Options for Texinfo output ---------------------------------------------- 154 | 155 | # Grouping the document tree into Texinfo files. List of tuples 156 | # (source start file, target name, title, author, 157 | # dir menu entry, description, category) 158 | texinfo_documents = [ 159 | (master_doc, 'PolyClipper', u'PolyClipper Documentation', 160 | author, 'J. Michael Owen', 'One line description of project.', 161 | 'Miscellaneous'), 162 | ] 163 | 164 | 165 | # -- Options for Epub output ------------------------------------------------- 166 | 167 | # Bibliographic Dublin Core info. 168 | epub_title = project 169 | 170 | # The unique identifier of the text. This can be a ISBN number 171 | # or the project homepage. 172 | # 173 | # epub_identifier = '' 174 | 175 | # A unique identification for the text. 176 | # 177 | # epub_uid = '' 178 | 179 | # A list of files that should not be packed into the epub file. 180 | epub_exclude_files = ['search.html'] 181 | 182 | 183 | # -- Extension configuration ------------------------------------------------- 184 | -------------------------------------------------------------------------------- /src/polyclipper2d.hh: -------------------------------------------------------------------------------- 1 | //---------------------------------PolyClipper--------------------------------// 2 | // Clip a faceted volume (polygon or polyhedron) by a set of planes in place. 3 | // 4 | // We use the convention that any portion of the faceted volume "below" the 5 | // plane is clipped, i.e., only the portion of the faceted volume "above" the 6 | // plane is retained. Above here is taken to mean the direction the plane 7 | // normal points in. This can also be defined in terms of a signed distance. 8 | // 9 | // We can define a plane by either a unit normal and point in the plane (n, p0), 10 | // or the plane normal and distance from the plane to the origin (n, d0). The 11 | // signed distance of a point (p) from the plane is then given by 12 | // 13 | // d_s = (p - p0).dot(n) = d0 + p.dot(n) 14 | // 15 | // The the volume with d_s > 0 is above the plane, while d_s < 0 is below. 16 | // 17 | // The algorithms herein are based on R3D as outlined in 18 | // Powell, D., & Abel, T. (2015). An exact general remeshing scheme applied to 19 | // physically conservative voxelization. Journal of Computational Physics, 297, 340–356. 20 | // 21 | // Created by J. Michael Owen, Tue Nov 28 10:00:51 PST 2017 22 | //----------------------------------------------------------------------------// 23 | #ifndef __PolyClipper2d__ 24 | #define __PolyClipper2d__ 25 | 26 | #include "polyclipper_adapter.hh" 27 | #include "polyclipper_plane.hh" 28 | #include "polyclipper_vector2d.hh" 29 | #include "polyclipper_utilities.hh" 30 | #include "polyclipper_serialize.hh" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | namespace PolyClipper { 39 | 40 | //------------------------------------------------------------------------------ 41 | // The 2D vertex struct, which we use to encode polygons, i.e., 42 | // polygons are specified as std::vector. 43 | //------------------------------------------------------------------------------ 44 | template> 45 | struct Vertex2d { 46 | using Vector = typename VA::VECTOR; 47 | Vector position; 48 | std::pair neighbors; 49 | int comp; 50 | mutable int ID; // convenient, but sneaky 51 | mutable std::set clips; // the planes (if any) that created this point 52 | Vertex2d() : position(VA::Vector(0.0,0.0)), neighbors(), comp(1), ID(-1), clips() {} 53 | Vertex2d(const Vector& pos) : position(pos), neighbors(), comp(1), ID(-1), clips() {} 54 | Vertex2d(const Vector& pos, const int c) : position(pos), neighbors(), comp(c), ID(-1), clips() {} 55 | Vertex2d(const Vertex2d& rhs) : position(rhs.position), neighbors(rhs.neighbors), comp(rhs.comp), ID(rhs.ID), clips(rhs.clips) {} 56 | Vertex2d& operator=(const Vertex2d& rhs) { position = rhs.position; neighbors = rhs.neighbors; comp = rhs.comp; ID = rhs.ID; clips = rhs.clips; return *this; } 57 | bool operator==(const Vertex2d& rhs) const { 58 | return (VA::equal(position, rhs.position) and 59 | neighbors == rhs.neighbors and 60 | comp == rhs.comp and 61 | ID == rhs.ID); 62 | } 63 | friend std::ostream& operator<<(std::ostream& os, const Vertex2d& v) { 64 | os << "Vertex2d[ " << VA::str(v.position) << " [" << v.neighbors.first << " " << v.neighbors.second << "] " << v.comp << " " << v.ID << "]"; 65 | return os; 66 | } 67 | }; 68 | 69 | //------------------------------------------------------------------------------ 70 | // Initialize a polygon given the vertex coordinates and connectivity. 71 | //------------------------------------------------------------------------------ 72 | template> 73 | void initializePolygon(std::vector>& poly, 74 | const std::vector& positions, 75 | const std::vector>& neighbors); 76 | 77 | //------------------------------------------------------------------------------ 78 | // Return a nicely formatted string representing the polygon. 79 | //------------------------------------------------------------------------------ 80 | template> 81 | std::string polygon2string(const std::vector>& poly); 82 | 83 | //------------------------------------------------------------------------------ 84 | // Compute the zeroth and first moment of a Polygon. 85 | //------------------------------------------------------------------------------ 86 | template> 87 | void moments(double& zerothMoment, typename VA::VECTOR& firstMoment, 88 | const std::vector>& polygon); 89 | 90 | //------------------------------------------------------------------------------ 91 | // Clip a polygon by planes. 92 | //------------------------------------------------------------------------------ 93 | template> 94 | void clipPolygon(std::vector>& poly, 95 | const std::vector>& planes); 96 | 97 | //------------------------------------------------------------------------------ 98 | // Collapse degenerate vertices. 99 | //------------------------------------------------------------------------------ 100 | template> 101 | void collapseDegenerates(std::vector>& poly, 102 | const double tol); 103 | 104 | //------------------------------------------------------------------------------ 105 | // Return the vertices ordered in faces. 106 | //------------------------------------------------------------------------------ 107 | template> 108 | std::vector> extractFaces(const std::vector>& poly); 109 | 110 | //------------------------------------------------------------------------------ 111 | // Compute the set of clips common to each face. 112 | //------------------------------------------------------------------------------ 113 | template> 114 | std::vector> commonFaceClips(const std::vector>& poly, 115 | const std::vector>& faces); 116 | 117 | //------------------------------------------------------------------------------ 118 | // Split a polygon into a set of triangles. 119 | //------------------------------------------------------------------------------ 120 | template> 121 | std::vector> splitIntoTriangles(const std::vector>& poly, 122 | const double tol = 0.0); 123 | 124 | } 125 | 126 | #include "polyclipper2dImpl.hh" 127 | 128 | #endif 129 | 130 | -------------------------------------------------------------------------------- /src/polyclipper3d.hh: -------------------------------------------------------------------------------- 1 | //---------------------------------PolyClipper--------------------------------// 2 | // Clip a faceted volume (polygon or polyhedron) by a set of planes in place. 3 | // 4 | // We use the convention that any portion of the faceted volume "below" the 5 | // plane is clipped, i.e., only the portion of the faceted volume "above" the 6 | // plane is retained. Above here is taken to mean the direction the plane 7 | // normal points in. This can also be defined in terms of a signed distance. 8 | // 9 | // We can define a plane by either a unit normal and point in the plane (n, p0), 10 | // or the plane normal and distance from the plane to the origin (n, d0). The 11 | // signed distance of a point (p) from the plane is then given by 12 | // 13 | // d_s = (p - p0).dot(n) = d0 + p.dot(n) 14 | // 15 | // The the volume with d_s > 0 is above the plane, while d_s < 0 is below. 16 | // 17 | // The algorithms herein are based on R3D as outlined in 18 | // Powell, D., & Abel, T. (2015). An exact general remeshing scheme applied to 19 | // physically conservative voxelization. Journal of Computational Physics, 297, 340–356. 20 | // 21 | // Created by J. Michael Owen, Tue Nov 28 10:00:51 PST 2017 22 | //----------------------------------------------------------------------------// 23 | #ifndef __PolyClipper3d_hh__ 24 | #define __PolyClipper3d_hh__ 25 | 26 | #include "polyclipper_adapter.hh" 27 | #include "polyclipper_plane.hh" 28 | #include "polyclipper_vector3d.hh" 29 | #include "polyclipper_utilities.hh" 30 | #include "polyclipper_serialize.hh" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | namespace PolyClipper { 39 | 40 | //------------------------------------------------------------------------------ 41 | // The 3D vertex struct, which we use to encode polyhedra, i.e., 42 | // polyhedra are specified as std::vector. 43 | //------------------------------------------------------------------------------ 44 | template> 45 | struct Vertex3d { 46 | using Vector = typename VA::VECTOR; 47 | Vector position; 48 | std::vector neighbors; 49 | int comp; 50 | mutable int ID; // convenient, but sneaky 51 | mutable std::set clips; // the planes (if any) that created this point 52 | Vertex3d() : position(VA::Vector(0.0, 0.0, 0.0)), neighbors(), comp(1), ID(-1), clips() {} 53 | Vertex3d(const Vector& pos) : position(pos), neighbors(), comp(1), ID(-1), clips() {} 54 | Vertex3d(const Vector& pos, const int c) : position(pos), neighbors(), comp(c), ID(-1), clips() {} 55 | Vertex3d(const Vertex3d& rhs) : position(rhs.position), neighbors(rhs.neighbors), comp(rhs.comp), ID(rhs.ID), clips(rhs.clips) {} 56 | Vertex3d& operator=(const Vertex3d& rhs) { position = rhs.position; neighbors = rhs.neighbors; comp = rhs.comp; ID = rhs.ID; clips = rhs.clips; return *this; } 57 | bool operator==(const Vertex3d& rhs) const { 58 | return (VA::equal(position, rhs.position) and 59 | neighbors == rhs.neighbors and 60 | comp == rhs.comp and 61 | ID == rhs.ID); 62 | } 63 | friend std::ostream& operator<<(std::ostream& os, const Vertex3d& v) { 64 | os << "Vertex3d[ " << VA::str(v.position) << " ["; 65 | for (const auto i: v.neighbors) os << i << " "; 66 | os << "] " << v.comp << " " << v.ID << "]"; 67 | return os; 68 | } 69 | }; 70 | 71 | //------------------------------------------------------------------------------ 72 | // Initialize a polyhedron given the vertex coordinates and connectivity. 73 | //------------------------------------------------------------------------------ 74 | template> 75 | void initializePolyhedron(std::vector>& poly, 76 | const std::vector& positions, 77 | const std::vector>& neighbors); 78 | 79 | //------------------------------------------------------------------------------ 80 | // Return a nicely formatted string representing the polyhedron. 81 | //------------------------------------------------------------------------------ 82 | template> 83 | std::string polyhedron2string(const std::vector>& poly); 84 | 85 | //------------------------------------------------------------------------------ 86 | // Compute the zeroth and first moment of a Polyhedron. 87 | //------------------------------------------------------------------------------ 88 | template> 89 | void moments(double& zerothMoment, typename VA::VECTOR& firstMoment, 90 | const std::vector>& polyhedron); 91 | 92 | //------------------------------------------------------------------------------ 93 | // Clip a polyhedron by planes. 94 | //------------------------------------------------------------------------------ 95 | template> 96 | void clipPolyhedron(std::vector>& poly, 97 | const std::vector>& planes); 98 | 99 | //------------------------------------------------------------------------------ 100 | // Collapse degenerate vertices. 101 | //------------------------------------------------------------------------------ 102 | template> 103 | void collapseDegenerates(std::vector>& poly, 104 | const double tol); 105 | 106 | //------------------------------------------------------------------------------ 107 | // Return the vertices ordered in faces. 108 | // Implicitly uses the convention that neighbors for each vertex are arranged 109 | // counter-clockwise viewed from the exterior. 110 | //------------------------------------------------------------------------------ 111 | template> 112 | std::vector> extractFaces(const std::vector>& poly); 113 | 114 | //------------------------------------------------------------------------------ 115 | // Compute the set of clips common to each face. 116 | //------------------------------------------------------------------------------ 117 | template> 118 | std::vector> commonFaceClips(const std::vector>& poly, 119 | const std::vector>& faces); 120 | 121 | //------------------------------------------------------------------------------ 122 | // Split a polyhedron into a set of tetrahedra. 123 | //------------------------------------------------------------------------------ 124 | template> 125 | std::vector> splitIntoTetrahedra(const std::vector>& poly, 126 | const double tol = 0.0); 127 | 128 | 129 | } 130 | 131 | #include "polyclipper3dImpl.hh" 132 | 133 | #endif 134 | 135 | -------------------------------------------------------------------------------- /test/testPolyClipperSerialize.py: -------------------------------------------------------------------------------- 1 | import unittest, random, string 2 | from PolyClipper import * 3 | 4 | # Create a global random number generator. 5 | rangen = random.Random() 6 | 7 | ntests = 100 8 | 9 | #------------------------------------------------------------------------------- 10 | # Serialize/deserialize unit tests 11 | #------------------------------------------------------------------------------- 12 | class TestPolyClipperSerialize(unittest.TestCase): 13 | 14 | # double 15 | def test_double(self): 16 | vals0 = [rangen.uniform(-100.0, 100.0) for i in range(ntests)] 17 | buf = bytes() 18 | for x in vals0: 19 | buf = serialize_double(x, buf) 20 | itr = 0 21 | vals1 = [] 22 | for i in range(ntests): 23 | x, itr = deserialize_double(itr, buf) 24 | vals1.append(x) 25 | assert vals1 == vals0 26 | 27 | # int 28 | def test_int(self): 29 | vals0 = [rangen.randint(-100, 100) for i in range(ntests)] 30 | buf = bytes() 31 | for x in vals0: 32 | buf = serialize_int(x, buf) 33 | itr = 0 34 | vals1 = [] 35 | for i in range(ntests): 36 | x, itr = deserialize_int(itr, buf) 37 | vals1.append(x) 38 | assert vals1 == vals0 39 | 40 | # size_t 41 | def test_size_t(self): 42 | vals0 = [rangen.randint(0, 1000) for i in range(ntests)] 43 | buf = bytes() 44 | for x in vals0: 45 | buf = serialize_size_t(x, buf) 46 | itr = 0 47 | vals1 = [] 48 | for i in range(ntests): 49 | x, itr = deserialize_size_t(itr, buf) 50 | vals1.append(x) 51 | assert vals1 == vals0 52 | 53 | # string 54 | def test_string(self): 55 | stuff = string.ascii_letters + string.digits + string.punctuation 56 | vals0 = [''.join(rangen.choice(stuff) for j in range(20)) for i in range(ntests)] 57 | buf = bytes() 58 | for x in vals0: 59 | buf = serialize_string(x, buf) 60 | itr = 0 61 | vals1 = [] 62 | for i in range(ntests): 63 | x, itr = deserialize_string(itr, buf) 64 | vals1.append(x) 65 | assert vals1 == vals0 66 | 67 | # Vector2d 68 | def test_Vector2d(self): 69 | vals0 = [Vector2d(rangen.uniform(-100.0, 100.0), 70 | rangen.uniform(-100.0, 100.0)) for i in range(ntests)] 71 | buf = bytes() 72 | for x in vals0: 73 | buf = serialize_Vector2d(x, buf) 74 | itr = 0 75 | vals1 = [] 76 | for i in range(ntests): 77 | x, itr = deserialize_Vector2d(itr, buf) 78 | vals1.append(x) 79 | assert vals1 == vals0 80 | 81 | 82 | # Vector3d 83 | def test_Vector3d(self): 84 | vals0 = [Vector3d(rangen.uniform(-100.0, 100.0), 85 | rangen.uniform(-100.0, 100.0), 86 | rangen.uniform(-100.0, 100.0)) for i in range(ntests)] 87 | buf = bytes() 88 | for x in vals0: 89 | buf = serialize_Vector3d(x, buf) 90 | itr = 0 91 | vals1 = [] 92 | for i in range(ntests): 93 | x, itr = deserialize_Vector3d(itr, buf) 94 | vals1.append(x) 95 | assert vals1 == vals0 96 | 97 | # Vertex2d 98 | def test_Vertex2d(self): 99 | vals0 = [Vertex2d(pos = Vector2d(rangen.uniform(-100.0, 100.0), 100 | rangen.uniform(-100.0, 100.0)), 101 | c = rangen.randint(-1, -1)) for i in range(ntests)] 102 | buf = bytes() 103 | for x in vals0: 104 | buf = serialize_Vertex2d(x, buf) 105 | itr = 0 106 | vals1 = [] 107 | for i in range(ntests): 108 | x, itr = deserialize_Vertex2d(itr, buf) 109 | vals1.append(x) 110 | assert vals1 == vals0 111 | 112 | # Vertex3d 113 | def test_Vertex3d(self): 114 | vals0 = [Vertex3d(pos = Vector3d(rangen.uniform(-100.0, 100.0), 115 | rangen.uniform(-100.0, 100.0), 116 | rangen.uniform(-100.0, 100.0)), 117 | c = rangen.randint(-1, -1)) for i in range(ntests)] 118 | buf = bytes() 119 | for x in vals0: 120 | buf = serialize_Vertex3d(x, buf) 121 | itr = 0 122 | vals1 = [] 123 | for i in range(ntests): 124 | x, itr = deserialize_Vertex3d(itr, buf) 125 | vals1.append(x) 126 | assert vals1 == vals0 127 | 128 | # Polygon (these will be toplogically invalid, but OK for our test) 129 | def test_Polygon(self): 130 | vals0 = [Polygon([Vertex2d(pos = Vector2d(rangen.uniform(-100.0, 100.0), 131 | rangen.uniform(-100.0, 100.0)), 132 | c = rangen.randint(-1, -1)) for i in range(ntests)]) for j in range(ntests)] 133 | buf = bytes() 134 | for x in vals0: 135 | buf = serialize_Polygon(x, buf) 136 | itr = 0 137 | vals1 = [] 138 | for i in range(ntests): 139 | x, itr = deserialize_Polygon(itr, buf) 140 | vals1.append(x) 141 | assert vals1 == vals0 142 | 143 | # Polyhedron (these will be toplogically invalid, but OK for our test) 144 | def test_Polyhedron(self): 145 | vals0 = [Polyhedron([Vertex3d(pos = Vector3d(rangen.uniform(-100.0, 100.0), 146 | rangen.uniform(-100.0, 100.0), 147 | rangen.uniform(-100.0, 100.0)), 148 | c = rangen.randint(-1, -1)) for i in range(ntests)]) for j in range(ntests)] 149 | buf = bytes() 150 | for x in vals0: 151 | buf = serialize_Polyhedron(x, buf) 152 | itr = 0 153 | vals1 = [] 154 | for i in range(ntests): 155 | x, itr = deserialize_Polyhedron(itr, buf) 156 | vals1.append(x) 157 | assert vals1 == vals0 158 | 159 | # Plane2d 160 | def test_Plane2d(self): 161 | vals0 = [Plane2d(p = Vector2d(rangen.uniform(-100.0, 100.0), 162 | rangen.uniform(-100.0, 100.0)), 163 | nhat = Vector2d(rangen.uniform(-1.0, 1.0), 164 | rangen.uniform(-1.0, 1.0)).unitVector(), 165 | id = rangen.randint(0, 1000)) for i in range(ntests)] 166 | buf = bytes() 167 | for x in vals0: 168 | buf = serialize_Plane2d(x, buf) 169 | itr = 0 170 | vals1 = [] 171 | for i in range(ntests): 172 | x, itr = deserialize_Plane2d(itr, buf) 173 | vals1.append(x) 174 | assert vals1 == vals0 175 | 176 | # Plane3d 177 | def test_Plane3d(self): 178 | vals0 = [Plane3d(p = Vector3d(rangen.uniform(-100.0, 100.0), 179 | rangen.uniform(-100.0, 100.0), 180 | rangen.uniform(-100.0, 100.0)), 181 | nhat = Vector3d(rangen.uniform(-1.0, 1.0), 182 | rangen.uniform(-1.0, 1.0), 183 | rangen.uniform(-1.0, 1.0)).unitVector(), 184 | id = rangen.randint(0, 1000)) for i in range(ntests)] 185 | buf = bytes() 186 | for x in vals0: 187 | buf = serialize_Plane3d(x, buf) 188 | itr = 0 189 | vals1 = [] 190 | for i in range(ntests): 191 | x, itr = deserialize_Plane3d(itr, buf) 192 | vals1.append(x) 193 | assert vals1 == vals0 194 | 195 | # std::vector 196 | def test_vector_of_Plane2d(self): 197 | vals0 = [[Plane2d(p = Vector2d(rangen.uniform(-100.0, 100.0), 198 | rangen.uniform(-100.0, 100.0)), 199 | nhat = Vector2d(rangen.uniform(-1.0, 1.0), 200 | rangen.uniform(-1.0, 1.0)).unitVector(), 201 | id = rangen.randint(0, 1000)) for i in range(ntests)] for j in range(ntests)] 202 | buf = bytes() 203 | for x in vals0: 204 | buf = serialize_vector_of_Plane2d(x, buf) 205 | itr = 0 206 | vals1 = [] 207 | for i in range(ntests): 208 | x, itr = deserialize_vector_of_Plane2d(itr, buf) 209 | vals1.append(x) 210 | assert vals1 == vals0 211 | 212 | # std::vector 213 | def test_vector_of_Plane3d(self): 214 | vals0 = [[Plane3d(p = Vector3d(rangen.uniform(-100.0, 100.0), 215 | rangen.uniform(-100.0, 100.0), 216 | rangen.uniform(-100.0, 100.0)), 217 | nhat = Vector3d(rangen.uniform(-1.0, 1.0), 218 | rangen.uniform(-1.0, 1.0), 219 | rangen.uniform(-1.0, 1.0)).unitVector(), 220 | id = rangen.randint(0, 1000)) for i in range(ntests)] for j in range(ntests)] 221 | buf = bytes() 222 | for x in vals0: 223 | buf = serialize_vector_of_Plane3d(x, buf) 224 | itr = 0 225 | vals1 = [] 226 | for i in range(ntests): 227 | x, itr = deserialize_vector_of_Plane3d(itr, buf) 228 | vals1.append(x) 229 | assert vals1 == vals0 230 | 231 | #------------------------------------------------------------------------------- 232 | # Execute the tests 233 | #------------------------------------------------------------------------------- 234 | if __name__ == "__main__": 235 | unittest.main() 236 | -------------------------------------------------------------------------------- /test/test_serialization.py: -------------------------------------------------------------------------------- 1 | #ATS:test(SELF, label="PolyClipper serialization tests") 2 | 3 | import unittest 4 | import string 5 | 6 | from PolyClipper import * 7 | 8 | # Create a global random number generator. 9 | import random 10 | rangen = random.Random() 11 | 12 | #------------------------------------------------------------------------------- 13 | class TestPolyClipperSerialization(unittest.TestCase): 14 | 15 | #--------------------------------------------------------------------------- 16 | # double 17 | #--------------------------------------------------------------------------- 18 | def test_double(self): 19 | buf = bytes() 20 | x = rangen.uniform(-1e100, 1e100) 21 | buf = serialize_double(x, buf) 22 | y, bufend = deserialize_double(0, buf) 23 | self.assertTrue(x == y, 24 | "%s != %s" % (x, y)) 25 | 26 | #--------------------------------------------------------------------------- 27 | # int 28 | #--------------------------------------------------------------------------- 29 | def test_int(self): 30 | buf = bytes() 31 | x = rangen.randint(-2**30, 2**30) 32 | buf = serialize_int(x, buf) 33 | y, bufend = deserialize_int(0, buf) 34 | self.assertTrue(x == y, 35 | "%s != %s" % (x, y)) 36 | 37 | #--------------------------------------------------------------------------- 38 | # string 39 | #--------------------------------------------------------------------------- 40 | def test_string(self): 41 | buf = bytes() 42 | x = "".join(random.choices(string.ascii_uppercase + 43 | string.ascii_lowercase + 44 | string.digits, k=1024)) 45 | buf = serialize_string(x, buf) 46 | y, bufend = deserialize_string(0, buf) 47 | self.assertTrue(x == y, 48 | "%s != %s" % (x, y)) 49 | 50 | #--------------------------------------------------------------------------- 51 | # Vector2d 52 | #--------------------------------------------------------------------------- 53 | def test_Vector2d(self): 54 | buf = bytes() 55 | x = Vector2d(rangen.uniform(-1e100, 1e100), 56 | rangen.uniform(-1e100, 1e100)) 57 | buf = serialize_Vector2d(x, buf) 58 | y, bufend = deserialize_Vector2d(0, buf) 59 | self.assertTrue(x == y, 60 | "%s != %s" % (x, y)) 61 | 62 | #--------------------------------------------------------------------------- 63 | # Vector3d 64 | #--------------------------------------------------------------------------- 65 | def test_Vector3d(self): 66 | buf = bytes() 67 | x = Vector3d(rangen.uniform(-1e100, 1e100), 68 | rangen.uniform(-1e100, 1e100), 69 | rangen.uniform(-1e100, 1e100)) 70 | buf = serialize_Vector3d(x, buf) 71 | y, bufend = deserialize_Vector3d(0, buf) 72 | self.assertTrue(x == y, 73 | "%s != %s" % (x, y)) 74 | 75 | #--------------------------------------------------------------------------- 76 | # Vertex2d 77 | #--------------------------------------------------------------------------- 78 | def test_Vertex2d(self): 79 | buf = bytes() 80 | x = Vertex2d() 81 | x.position = Vector2d(rangen.uniform(-1e100, 1e100), 82 | rangen.uniform(-1e100, 1e100)) 83 | x.neighbors = (1, 2) 84 | x.comp = rangen.choice((-2, -1, 0, 1, 2)) 85 | x.ID = rangen.randint(-2**30, 2**30) 86 | buf = serialize_Vertex2d(x, buf) 87 | y, bufend = deserialize_Vertex2d(0, buf) 88 | self.assertTrue(x == y, 89 | "%s != %s" % (x, y)) 90 | 91 | #--------------------------------------------------------------------------- 92 | # Vertex3d 93 | #--------------------------------------------------------------------------- 94 | def test_Vertex3d(self): 95 | buf = bytes() 96 | x = Vertex3d() 97 | x.position = Vector3d(rangen.uniform(-1e100, 1e100), 98 | rangen.uniform(-1e100, 1e100), 99 | rangen.uniform(-1e100, 1e100)) 100 | x.neighbors = [1, 2, 10, 100, 14] 101 | x.comp = rangen.choice((-2, -1, 0, 1, 2)) 102 | x.ID = rangen.randint(-2**30, 2**30) 103 | buf = serialize_Vertex3d(x, buf) 104 | y, bufend = deserialize_Vertex3d(0, buf) 105 | self.assertTrue(x == y, 106 | "%s != %s" % (x, y)) 107 | 108 | #--------------------------------------------------------------------------- 109 | # Polygon 110 | #--------------------------------------------------------------------------- 111 | def test_Polygon(self): 112 | buf = bytes() 113 | x = Polygon() 114 | N = 20 115 | for i in range(N): 116 | v = Vertex2d() 117 | v.position = Vector2d(rangen.uniform(-1e100, 1e100), 118 | rangen.uniform(-1e100, 1e100)) 119 | v.neighbors = ((i - 1) % N, 120 | (i + 1) % N) 121 | v.comp = rangen.choice((-2, -1, 0, 1, 2)) 122 | v.ID = i 123 | x.append(v) 124 | assert len(x) == N 125 | buf = serialize_Polygon(x, buf) 126 | y, bufend = deserialize_Polygon(0, buf) 127 | self.assertTrue(x == y, 128 | "%s != %s" % (x, y)) 129 | 130 | #--------------------------------------------------------------------------- 131 | # Polyhedron 132 | #--------------------------------------------------------------------------- 133 | def test_Polyhedron(self): 134 | buf = bytes() 135 | x = Polyhedron() 136 | N = 20 137 | for i in range(N): 138 | v = Vertex3d() 139 | v.position = Vector3d(rangen.uniform(-1e100, 1e100), 140 | rangen.uniform(-1e100, 1e100), 141 | rangen.uniform(-1e100, 1e100)) 142 | v.neighbors = rangen.choices(range(N), k=3) 143 | v.comp = rangen.choice((-2, -1, 0, 1, 2)) 144 | v.ID = i 145 | x.append(v) 146 | assert len(x) == N 147 | buf = serialize_Polyhedron(x, buf) 148 | y, bufend = deserialize_Polyhedron(0, buf) 149 | self.assertTrue(x == y, 150 | "%s != %s" % (x, y)) 151 | 152 | #--------------------------------------------------------------------------- 153 | # Plane2d 154 | #--------------------------------------------------------------------------- 155 | def test_Plane2d(self): 156 | buf = bytes() 157 | x = Plane2d(p = Vector2d(rangen.uniform(-1e100, 1e100), 158 | rangen.uniform(-1e100, 1e100)), 159 | nhat = Vector2d(rangen.uniform(-1e100, 1e100), 160 | rangen.uniform(-1e100, 1e100)).unitVector(), 161 | id = rangen.randint(-2**30, 2**30)) 162 | buf = serialize_Plane2d(x, buf) 163 | y, bufend = deserialize_Plane2d(0, buf) 164 | self.assertTrue(x == y, 165 | "%s != %s" % (x, y)) 166 | 167 | #--------------------------------------------------------------------------- 168 | # Plane3d 169 | #--------------------------------------------------------------------------- 170 | def test_Plane3d(self): 171 | buf = bytes() 172 | x = Plane3d(p = Vector3d(rangen.uniform(-1e100, 1e100), 173 | rangen.uniform(-1e100, 1e100), 174 | rangen.uniform(-1e100, 1e100)), 175 | nhat = Vector3d(rangen.uniform(-1e100, 1e100), 176 | rangen.uniform(-1e100, 1e100), 177 | rangen.uniform(-1e100, 1e100)).unitVector(), 178 | id = rangen.randint(-2**30, 2**30)) 179 | buf = serialize_Plane3d(x, buf) 180 | y, bufend = deserialize_Plane3d(0, buf) 181 | self.assertTrue(x == y, 182 | "%s != %s" % (x, y)) 183 | 184 | #--------------------------------------------------------------------------- 185 | # vector 186 | #--------------------------------------------------------------------------- 187 | def test_vector_of_Plane2d(self): 188 | buf = bytes() 189 | N = 20 190 | x = [Plane2d(p = Vector2d(rangen.uniform(-1e100, 1e100), 191 | rangen.uniform(-1e100, 1e100)), 192 | nhat = Vector2d(rangen.uniform(-1e100, 1e100), 193 | rangen.uniform(-1e100, 1e100)).unitVector(), 194 | id = rangen.randint(-2**30, 2**30)) for i in range(N)] 195 | assert len(x) == N 196 | buf = serialize_vector_of_Plane2d(x, buf) 197 | y, bufend = deserialize_vector_of_Plane2d(0, buf) 198 | self.assertTrue(x == y, 199 | "%s != %s" % (x, y)) 200 | 201 | #--------------------------------------------------------------------------- 202 | # Plane3d 203 | #--------------------------------------------------------------------------- 204 | def test_Plane3d(self): 205 | buf = bytes() 206 | N = 20 207 | x = [Plane3d(p = Vector3d(rangen.uniform(-1e100, 1e100), 208 | rangen.uniform(-1e100, 1e100), 209 | rangen.uniform(-1e100, 1e100)), 210 | nhat = Vector3d(rangen.uniform(-1e100, 1e100), 211 | rangen.uniform(-1e100, 1e100), 212 | rangen.uniform(-1e100, 1e100)).unitVector(), 213 | id = rangen.randint(-2**30, 2**30)) for i in range(N)] 214 | assert len(x) == N 215 | buf = serialize_vector_of_Plane3d(x, buf) 216 | y, bufend = deserialize_vector_of_Plane3d(0, buf) 217 | self.assertTrue(x == y, 218 | "%s != %s" % (x, y)) 219 | 220 | #------------------------------------------------------------------------------- 221 | # Run the tests 222 | #------------------------------------------------------------------------------- 223 | if __name__ == "__main__": 224 | unittest.main() 225 | -------------------------------------------------------------------------------- /src/PYB11/PolyClipper_PYB11.py: -------------------------------------------------------------------------------- 1 | """ 2 | PolyClipper module. 3 | 4 | Binds the PolyClipper geometry operations for clipping polygons & polyhedra. 5 | """ 6 | 7 | from PYB11Generator import * 8 | import types 9 | 10 | # Include files. 11 | PYB11includes = ['"polyclipper2d.hh"', 12 | '"polyclipper3d.hh"', 13 | '"polyclipper_vector2d.hh"', 14 | '"polyclipper_vector3d.hh"', 15 | '"polyclipper_plane.hh"', 16 | '"polyclipper_serialize.hh"'] 17 | 18 | PYB11namespaces = ["PolyClipper"] 19 | 20 | PYB11preamble = """ 21 | using Polygon = std::vector>; 22 | using Polyhedron = std::vector>; 23 | using Plane2d = PolyClipper::Plane>; 24 | using Plane3d = PolyClipper::Plane>; 25 | """ 26 | 27 | #------------------------------------------------------------------------------- 28 | # Import the types defined in this module 29 | #------------------------------------------------------------------------------- 30 | from Vector2d import * 31 | from Vector3d import * 32 | from Vertex2d import * 33 | from Vertex3d import * 34 | from Plane import * 35 | 36 | #------------------------------------------------------------------------------- 37 | # Polygon & Polyhedron 38 | #------------------------------------------------------------------------------- 39 | Polygon = PYB11_bind_vector("PolyClipper::Vertex2d<>", opaque=True, local=True) 40 | Polyhedron = PYB11_bind_vector("PolyClipper::Vertex3d<>", opaque=True, local=True) 41 | 42 | # We also use a few other std::vector types 43 | vector_of_char = PYB11_bind_vector("char", opaque=True, local=True) 44 | #vector_of_Plane2d = PYB11_bind_vector("Plane2d", opaque=True, local=True) 45 | #vector_of_Plane3d = PYB11_bind_vector("Plane3d", opaque=True, local=True) 46 | 47 | #------------------------------------------------------------------------------- 48 | # Plane 49 | #------------------------------------------------------------------------------- 50 | Plane2d = PYB11TemplateClass(Plane, template_parameters="internal::VectorAdapter") 51 | Plane3d = PYB11TemplateClass(Plane, template_parameters="internal::VectorAdapter") 52 | 53 | #------------------------------------------------------------------------------- 54 | # Polygon methods. 55 | #------------------------------------------------------------------------------- 56 | def initializePolygon(poly = "Polygon&", 57 | positions = "const std::vector&", 58 | neighbors = "const std::vector>&"): 59 | "Initialize a PolyClipper::Polygon from vertex positions and vertex neighbors." 60 | return "void" 61 | 62 | @PYB11cppname("polygon2string<>") 63 | def polygon2string(poly = "Polygon&"): 64 | "Return a formatted string representation for a PolyClipper::Polygon." 65 | return "std::string" 66 | 67 | @PYB11implementation("""[](const Polygon& self) { 68 | double zerothMoment; 69 | Vector2d firstMoment; 70 | moments(zerothMoment, firstMoment, self); 71 | return py::make_tuple(zerothMoment, firstMoment); 72 | }""") 73 | @PYB11pycppname("moments") 74 | def momentsPolygon(poly = "const Polygon&"): 75 | "Compute the zeroth and first moment of a PolyClipper::Polygon." 76 | return "py::tuple" 77 | 78 | def clipPolygon(poly = "Polygon&", 79 | planes = "const std::vector&"): 80 | "Clip a PolyClipper::Polygon with a collection of planes." 81 | return "void" 82 | 83 | @PYB11pycppname("collapseDegenerates") 84 | def collapseDegeneratesPolygon(poly = "Polygon&", 85 | tol = "const double"): 86 | "Collapse edges in a PolyClipper::Polygon below the given tolerance." 87 | return "void" 88 | 89 | def extractFaces(poly = "const Polygon&"): 90 | "Compute the faces (as pairs of vertex indices) for the Polygon" 91 | return "std::vector>" 92 | 93 | def commonFaceClips(poly = "const Polygon&", 94 | faces = "const std::vector>&"): 95 | "Find the common clipping planes for each face" 96 | return "std::vector>" 97 | 98 | def splitIntoTriangles(poly = "const Polygon&", 99 | tol = ("const double", "0.0")): 100 | """Split a PolyClipper::Polygon into triangles. 101 | The result is returned as a vector>, where each inner vector is a triple of 102 | ints representing vertex indices in the input Polygon.""" 103 | return "std::vector>" 104 | 105 | #------------------------------------------------------------------------------- 106 | # Polyhedron methods. 107 | #------------------------------------------------------------------------------- 108 | def initializePolyhedron(poly = "Polyhedron&", 109 | positions = "const std::vector&", 110 | neighbors = "const std::vector>&"): 111 | "Initialize a PolyClipper::Polyhedron from vertex positions and vertex neighbors." 112 | return "void" 113 | 114 | @PYB11cppname("polyhedron2string<>") 115 | def polyhedron2string(poly = "Polyhedron&"): 116 | "Return a formatted string representation for a PolyClipper::Polyhedron." 117 | return "std::string" 118 | 119 | @PYB11implementation("""[](const Polyhedron& self) { 120 | double zerothMoment; 121 | Vector3d firstMoment; 122 | moments(zerothMoment, firstMoment, self); 123 | return py::make_tuple(zerothMoment, firstMoment); 124 | }""") 125 | @PYB11pycppname("moments") 126 | def momentsPolyhedron(poly = "const Polyhedron&"): 127 | "Compute the zeroth and first moment of a PolyClipper::Polyhedron." 128 | return "py::tuple" 129 | 130 | def clipPolyhedron(poly = "Polyhedron&", 131 | planes = "const std::vector&"): 132 | "Clip a PolyClipper::Polyhedron with a collection of planes." 133 | return "void" 134 | 135 | @PYB11pycppname("collapseDegenerates") 136 | def collapseDegeneratesPolyhedron(poly = "Polyhedron&", 137 | tol = "const double"): 138 | "Collapse edges in a PolyClipper::Polyhedron below the given tolerance." 139 | return "void" 140 | 141 | @PYB11pycppname("extractFaces") 142 | def extractFacesPolyhedron(poly = "const Polyhedron&"): 143 | "Compute the faces (as pairs of vertex indices) for the Polyhedron" 144 | return "std::vector>" 145 | 146 | @PYB11pycppname("commonFaceClips") 147 | def commonFaceClipsPolyhedron(poly = "const Polyhedron&", 148 | faces = "const std::vector>&"): 149 | "Find the common clipping planes for each face" 150 | return "std::vector>" 151 | 152 | def splitIntoTetrahedra(poly = "const Polyhedron&", 153 | tol = ("const double", "0.0")): 154 | """Split a PolyClipper::Polyhedron into tetrahedra. 155 | The result is returned as a vector>, where each inner vector is a set of four 156 | ints representing vertex indices in the input Polyhedron.""" 157 | return "std::vector>" 158 | 159 | #------------------------------------------------------------------------------- 160 | # Serialization/deserialization 161 | # I'll get cute here and use python code generation to make the various versions 162 | #------------------------------------------------------------------------------- 163 | for (cppname, pyname, template_arg) in (("double", "double", ""), 164 | ("int", "int", ""), 165 | ("size_t", "size_t", ""), 166 | ("std::string", "string", ""), 167 | ("Vector2d", "Vector2d", ">"), 168 | ("Vector3d", "Vector3d", ">"), 169 | ("Vertex2d<>", "Vertex2d", ">"), 170 | ("Vertex3d<>", "Vertex3d", ">"), 171 | ("Polygon", "Polygon", ">"), 172 | ("Polyhedron", "Polyhedron", ">"), 173 | ("Plane2d", "Plane2d", ">"), 174 | ("Plane3d", "Plane3d", ">"), 175 | ("std::vector", "vector_of_Plane2d", ">"), 176 | ("std::vector", "vector_of_Plane3d", ">")): 177 | exec(""" 178 | @PYB11implementation(''' 179 | [](const {cppname}& val, py::bytes& buffer) -> py::bytes {{ 180 | std::string sbuf(buffer); 181 | internal::serialize{template_arg}(val, sbuf); 182 | return py::bytes(sbuf); 183 | }}''') 184 | def serialize_{pyname}(val = "const {cppname}&", 185 | buffer = "py::bytes&"): 186 | "Serialize a(n) {cppname} to the given buffer stream. Returns a new buffer (different than C++ method)." 187 | return "py::bytes" 188 | 189 | @PYB11implementation(''' 190 | [](int itr_pos, const std::string& buffer) -> py::tuple {{ 191 | auto itr = buffer.begin() + itr_pos; 192 | {cppname} val; 193 | internal::deserialize{template_arg}(val, itr, buffer.end()); 194 | return py::make_tuple(val, int(std::distance(buffer.begin(), itr))); 195 | }}''') 196 | def deserialize_{pyname}(itr_pos = "int", 197 | buffer = "const std::string&"): 198 | "Deserialize {cppname} starting at itr_pos in buffer. Returns ({cppname}, new_itr_pos)" 199 | return "py::tuple" 200 | 201 | """.format(cppname = cppname, 202 | pyname = pyname, 203 | template_arg = template_arg)) 204 | 205 | #------------------------------------------------------------------------------- 206 | # Miscellaneous 207 | #------------------------------------------------------------------------------- 208 | @PYB11cppname("internal::dumpSerializedState") 209 | def dumpSerializedState(buffer = "const std::string&", 210 | filename = ("std::string", '"PolyClipper"')): 211 | "Write a char buffer to a binary file with randomly generated name extension" 212 | return "std::string" 213 | -------------------------------------------------------------------------------- /src/polyclipper_utilities.hh: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // A collection of internal utilities common in PolyClipper. 3 | // 4 | // These largely repoduce things in Spheral to make PolyClipper standalone. 5 | //------------------------------------------------------------------------------ 6 | #ifndef __polyclipper_utilities__ 7 | #define __polyclipper_utilities__ 8 | 9 | #include "polyclipper_vector2d.hh" 10 | #include "polyclipper_vector3d.hh" 11 | #include "polyclipper_plane.hh" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace PolyClipper { 24 | //------------------------------------------------------------------------------ 25 | // An exception to indicate an internal PolyClipper error 26 | //------------------------------------------------------------------------------ 27 | class PolyClipperError: public std::exception { 28 | public: 29 | std::string mMsg; 30 | PolyClipperError(const std::string msg): std::exception(), mMsg(msg) {} 31 | virtual const char* what() const throw() { return mMsg.c_str(); } 32 | }; 33 | 34 | //------------------------------------------------------------------------------ 35 | // Define an assert command with optional message 36 | // - PCASSERT is only active when compiled with -DNDEBUG 37 | // - PCALWAYSASSERT is always active 38 | //------------------------------------------------------------------------------ 39 | #ifndef NDEBUG 40 | # define PCASSERT2(condition, message) \ 41 | do { \ 42 | if (! (condition)) { \ 43 | std::ostringstream s; \ 44 | s << "PolyCliper ERROR: Assertion `" #condition "` failed in " \ 45 | << __FILE__ << " line " << __LINE__ << ": \n" << message << "\n";\ 46 | std::cerr << s.str(); \ 47 | throw PolyClipperError(s.str()); \ 48 | } \ 49 | } while (false) 50 | # define PCALWAYSASSERT2(condition, message) PCASSERT2(condition, message) 51 | #else 52 | # define PCASSERT2(condition, message) do { } while (false) 53 | # define PCALWAYSASSERT2(condition, message) \ 54 | do { \ 55 | if (! (condition)) { \ 56 | throw PolyClipperError(std::string()); \ 57 | } \ 58 | } while (false) 59 | #endif 60 | 61 | #define PCASSERT(condition) PCASSERT2(condition, #condition) 62 | #define PCALWAYSASSERT(condition) PCALWAYSASSERT2(condition, #condition) 63 | 64 | namespace internal { 65 | 66 | //------------------------------------------------------------------------------ 67 | // Return the sign of the argument determined as follows: 68 | // 69 | // x > 0 -> sgn0(x) = 1 70 | // x = 0 -> sgn0(x) = 0 71 | // x < 0 -> sgn0(x) = -1 72 | //------------------------------------------------------------------------------ 73 | inline 74 | double 75 | sgn0(const double x) { 76 | return (x > 0.0 ? 1.0 : 77 | x < 0.0 ? -1.0 : 78 | 0.0); 79 | } 80 | 81 | //------------------------------------------------------------------------------ 82 | // Return the sign of the argument determined as follows: 83 | // 84 | // x >= 0 -> sgn(x) = 1 85 | // x < 0 -> sgn(x) = -1 86 | //------------------------------------------------------------------------------ 87 | inline 88 | double 89 | sgn(const double x) { 90 | return (x >= 0.0 ? 1.0 : -1.0); 91 | } 92 | 93 | //------------------------------------------------------------------------------ 94 | // Fuzzy comparisons. 95 | //------------------------------------------------------------------------------ 96 | template 97 | inline 98 | bool fuzzyEqual(const DataType& lhs, const DataType& rhs, 99 | const double fuzz = 1.0e-15) { 100 | return std::abs(lhs - rhs)/std::max(1.0, std::abs(lhs) + std::abs(rhs)) < fuzz; 101 | } 102 | 103 | template 104 | inline 105 | bool fuzzyLessThanOrEqual(const DataType& lhs, const DataType& rhs, 106 | const double fuzz = 1.0e-15) { 107 | return lhs < rhs || fuzzyEqual(lhs, rhs, fuzz); 108 | } 109 | 110 | template 111 | inline 112 | bool fuzzyGreaterThanOrEqual(const DataType& lhs, const DataType& rhs, 113 | const double fuzz = 1.0e-15) { 114 | return lhs > rhs || fuzzyEqual(lhs, rhs, fuzz); 115 | } 116 | 117 | template 118 | inline 119 | bool distinctlyLessThan(const DataType& lhs, const DataType& rhs, 120 | const double fuzz = 1.0e-15) { 121 | return lhs < rhs && !fuzzyEqual(lhs, rhs, fuzz); 122 | } 123 | 124 | template 125 | inline 126 | bool distinctlyGreaterThan(const DataType& lhs, const DataType& rhs, 127 | const double fuzz = 1.0e-15) { 128 | return lhs > rhs && !fuzzyEqual(lhs, rhs, fuzz); 129 | } 130 | 131 | //------------------------------------------------------------------------------ 132 | // safeInv 133 | // 134 | // Take the inverse of a number, turning it to zero if the argument is 0.0. 135 | //------------------------------------------------------------------------------ 136 | template 137 | inline 138 | Value 139 | safeInv(const Value& x, 140 | const double fuzz = 1.0e-30) { 141 | return sgn(x)/std::max(fuzz, std::abs(x)); 142 | } 143 | 144 | //------------------------------------------------------------------------------ 145 | // removeElements 146 | // 147 | // Removes the specified elements by index from a std::vector. 148 | // This is needed because there doesn't seem to be a good solution for this in 149 | // the STL. The std::vector::erase method involves N^2 operations when you're 150 | // removing many elements, while the std::remove and std::remove_if do not work 151 | // for removing elements by index/iterator. 152 | //------------------------------------------------------------------------------ 153 | template 154 | inline 155 | void 156 | removeElements(std::vector& vec, 157 | const std::vector& elements) { 158 | 159 | // Is there anything to do? 160 | if (elements.size() > 0) { 161 | 162 | const index_t originalSize = vec.size(); 163 | const index_t newSize = originalSize - elements.size(); 164 | 165 | // Remove the elements. 166 | // We prefer not to use the vector::erase here 'cause if we're removing 167 | // many elements the copy and move behaviour of erase can make this 168 | // an N^2 thing. Yuck! 169 | typename std::vector::const_iterator delItr = elements.begin(); 170 | typename std::vector::const_iterator endItr = elements.end(); 171 | index_t i = *delItr; 172 | index_t j = i + 1; 173 | ++delItr; 174 | while (j != originalSize and delItr != endItr) { 175 | if (j == *delItr) { 176 | ++delItr; 177 | ++j; 178 | } else { 179 | vec[i] = vec[j]; 180 | ++i; 181 | ++j; 182 | } 183 | } 184 | if (j != originalSize) std::copy(vec.begin() + j, vec.end(), vec.begin() + i); 185 | 186 | // Resize vec to it's new size. 187 | vec.erase(vec.begin() + newSize, vec.end()); 188 | } 189 | } 190 | 191 | //------------------------------------------------------------------------------ 192 | // Compare a plane and point. 193 | //------------------------------------------------------------------------------ 194 | template 195 | inline 196 | int compare(const Plane& plane, 197 | const typename VA::VECTOR& point) { 198 | const auto sgndist = plane.dist + VA::dot(plane.normal, point); 199 | if (std::abs(sgndist) < 1.0e-10) return 0; 200 | return sgn0(sgndist); 201 | } 202 | 203 | //------------------------------------------------------------------------------ 204 | // Intersect a line-segment with a plane. 205 | //------------------------------------------------------------------------------ 206 | template 207 | inline 208 | typename VA::VECTOR 209 | segmentPlaneIntersection(const typename VA::VECTOR& a, // line-segment begin 210 | const typename VA::VECTOR& b, // line-segment end 211 | const Plane& plane) { // plane 212 | const auto asgndist = plane.dist + VA::dot(plane.normal, a); 213 | const auto bsgndist = plane.dist + VA::dot(plane.normal, b); 214 | PCASSERT(asgndist != bsgndist); 215 | return VA::div(VA::sub(VA::mul(a, bsgndist), VA::mul(b, asgndist)), bsgndist - asgndist); 216 | } 217 | 218 | //------------------------------------------------------------------------------ 219 | // Dump a serialized state to a file with a randomly augmented name. 220 | //------------------------------------------------------------------------------ 221 | inline 222 | std::string 223 | dumpSerializedState(const std::string& buffer, 224 | std::string filename = "PolyClipper") { 225 | std::string digits("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); 226 | std::random_device rd; 227 | std::mt19937 generator(rd()); 228 | std::shuffle(digits.begin(), digits.end(), generator); 229 | filename += "_" + digits.substr(0, 20) + ".bin"; 230 | std::ofstream os(filename, std::ios::out | std::ios::binary); 231 | os.write(&(buffer[0]), buffer.size()); 232 | os.close(); 233 | return "Wrote " + std::to_string(buffer.size()) + " bytes to " + filename; 234 | } 235 | 236 | } 237 | } 238 | 239 | //------------------------------------------------------------------------------ 240 | // Vector2d ostream 241 | //------------------------------------------------------------------------------ 242 | inline 243 | std::ostream& 244 | operator<<(std::ostream& os, const PolyClipper::Vector2d& vec) { 245 | os << "( " << vec.x << " " << vec.y << ")"; 246 | return os; 247 | } 248 | 249 | //------------------------------------------------------------------------------ 250 | // double * Vector2d 251 | //------------------------------------------------------------------------------ 252 | inline 253 | PolyClipper::Vector2d 254 | operator*(const double lhs, const PolyClipper::Vector2d& rhs) { 255 | return rhs*lhs; 256 | } 257 | 258 | //------------------------------------------------------------------------------ 259 | // Vector3d ostream 260 | //------------------------------------------------------------------------------ 261 | inline 262 | std::ostream& 263 | operator<<(std::ostream& os, const PolyClipper::Vector3d& vec) { 264 | os << "( " << vec.x << " " << vec.y << " " << vec.z << ")"; 265 | return os; 266 | } 267 | 268 | //------------------------------------------------------------------------------ 269 | // double * Vector3d 270 | //------------------------------------------------------------------------------ 271 | inline 272 | PolyClipper::Vector3d 273 | operator*(const double lhs, const PolyClipper::Vector3d& rhs) { 274 | return rhs*lhs; 275 | } 276 | 277 | //------------------------------------------------------------------------------ 278 | // Plane ostream 279 | //------------------------------------------------------------------------------ 280 | template 281 | inline 282 | std::ostream& 283 | operator<<(std::ostream& os, const PolyClipper::Plane& plane) { 284 | os << "Plane[ " << plane.dist << " " << VA::str(plane.normal) << " " << plane.ID << "]"; 285 | return os; 286 | } 287 | 288 | #endif 289 | -------------------------------------------------------------------------------- /docs/concepts.rst: -------------------------------------------------------------------------------- 1 | ######################################## 2 | PolyClipper concepts 3 | ######################################## 4 | 5 | PolyClipper only has a few data types (``Vector2d``, ``Vector3d``, ``Plane2d``, ``Plane3d``, ``Vertex2d``, and ``Vertex3d``), all defined inside the C++ namespace ``PolyClipper``. The main methods of PolyClipper are free functions for clipping and manipulating polytopes (i.e., polygons and polyhedra), which are represented as collections of ``Vertex2d`` (for polygons) and ``Vertex3d`` (for polyhedra). Note that while PolyClipper provides native (``Vector2d``, ``Vector3d``) types, PolyClipper's functions are templated on the Vector type (actually a trait class describing the Vector operations), allowing the C++ user to use their own native geometric Vectors if so desired. The built in Python module provided by PolyClipper is instantiated using PolyClipper's native Vector's, so if you want to provide Python versions with your own Vector types you must bind those yourself. Further discussion and examples of this generalized C++ functionality can be found in :ref:`Using PolyClipper with a user defined Vector type`. 6 | 7 | Collections of vertices encapsulate all the necessary information to specify Polygons and Polyhedra. This works because vertices contain their connectivity to neighbor vertices in the object. For example, consider a hexagon as an example of a Polygon: 8 | 9 | .. image:: Vertex2d.* 10 | :width: 200 11 | :alt: You should see a labeled polygon here 12 | 13 | The ``Vertex2d`` PolyClipper structure for vertex 2 includes the links to vertices (1, 3) as neighbors in counterclockwise order. 14 | 15 | Similarly a cube can be represented as a collection of ``Vertex3d``: 16 | 17 | .. image:: Cube.* 18 | :width: 200 19 | :alt: You should see a labeled cube here 20 | 21 | In this case the neighbors of vertex 6 will be (5, 2, 7), listed counterclockwise as viewed from the exterior of the polyhedron. Note while vertices of Polygons will always have 2 neighbors, vertices of Polyhedra can have 3 or more neighbors. A pyramid is a simple example of this: 22 | 23 | .. image:: Pyramid.* 24 | :width: 200 25 | :alt: You should see a labeled pyramid here 26 | 27 | Here we can see that vertex 4 will have 4 neighbors: (0, 1, 2, 3). 28 | 29 | .. note:: 30 | In the C++ interface we use ``std::vector>`` to represent polygons, and ``std::vector>`` for polyhedra. The examples below use the Python interface where ``Polygon`` and ``Polyhedron`` are types in Python, but in reality under the hood these are simply typedefs for these ``std::vector`` collections of vertices. 31 | 32 | 33 | Clipping operations 34 | ======================================== 35 | 36 | PolyClippers main functionality is the clipping of Polygons/Polyhedra by arbitrary planes. Planes are defined by a unit normal and distance from the plane to the origin, :math:`(\hat{n}, d)`. Note given some point :math:`\vec{p_0}` in the plane, :math:`d = -\hat{n}\cdot\vec{p}_0`. 37 | 38 | Points are defined as above a plane when they are on the side the plane normal :math:`\hat{n}` points toward, and below if on the opposite side. This can be defined mathematically in terms of the signed distance of a point :math:`\vec{p}` from the plane: 39 | 40 | .. math:: 41 | d_s(\vec{p}) = (\vec{p} - \vec{p}_0) \cdot \hat{n} = d + \vec{p} \cdot \hat{n}, 42 | 43 | where again :math:`\vec{p}_0` is some point in the plane. Now if :math:`d_s(\vec{p}) > 0` then :math:`\vec{p}` is above the plane, while :math:`d_s(\vec{p}) < 0` implies it is below. 44 | 45 | Clipping a Polygon/Polyhedron in PolyClipper is defined such that the portion of the volume above the plane is retained, while that below is clipped and removed. Consider for instance a non-convex polygon: 46 | 47 | .. image:: notched_polygon.* 48 | :width: 400 49 | :alt: You should see a non-convex polygon 50 | 51 | In this example the coordinates are shown in magenta on the interior of the polygon, and the vertex numbers in black on the exterior. In PolyClippers Python interface this Polygon can be constructed using:: 52 | 53 | notched_points = [Vector2d(*coords) 54 | for coords in [(0,0), (4,0), (4,2), (3,2), (2,1), (1,2), (0,2)]] 55 | n = len(notched_points) 56 | notched_neighbors = [[(i - 1) % n, (i + 1) % n] for i in range(n)] 57 | poly = Polygon() 58 | initializePolygon(poly, notched_points, notched_neighbors) 59 | print "Starting poly: ", list(poly) 60 | 61 | where we have used the fact the vertices are numbered sequentially in counter-clockwise order to generate the neighbor lists for each vertex. Since Polygons are simply lists of ``Vertex2d``, the final print outputs something like:: 62 | 63 | Starting poly: [{pos=(0.000000 0.000000), neighbors=(6 1), ID=-1, clips=( )}, 64 | {pos=(4.000000 0.000000), neighbors=(0 2), ID=-1, clips=( )}, 65 | {pos=(4.000000 2.000000), neighbors=(1 3), ID=-1, clips=( )}, 66 | {pos=(3.000000 2.000000), neighbors=(2 4), ID=-1, clips=( )}, 67 | {pos=(2.000000 1.000000), neighbors=(3 5), ID=-1, clips=( )}, 68 | {pos=(1.000000 2.000000), neighbors=(4 6), ID=-1, clips=( )}, 69 | {pos=(0.000000 2.000000), neighbors=(5 0), ID=-1, clips=( )}] 70 | 71 | 72 | Clipping by a single plane 73 | ---------------------------------------- 74 | 75 | We can clip this Polygon by a single plane defined by a {point, normal} of :math:`\{(3, 1), \widehat{{(-1, 0.5)}}\}` (where the wide-hat symbol implies constructing the unit vector) with:: 76 | 77 | planes = [Plane2d(Vector2d(3, 1), Vector2d(-1, 0.5).unitVector(), 10)] 78 | clipPolygon(poly, planes) 79 | print "Single clip: ", list(poly) 80 | 81 | resulting in 82 | 83 | .. image:: notched_polygon_clip1.* 84 | :width: 400 85 | :alt: You should see a clipped polygon 86 | 87 | and vertices now printed as:: 88 | 89 | Single clip: [{pos=(0.000000 0.000000), neighbors=(4 5), ID=0, clips=( )}, 90 | {pos=(3.000000 2.000000), neighbors=(6 2), ID=1, clips=( )}, 91 | {pos=(2.000000 1.000000), neighbors=(1 3), ID=2, clips=( )}, 92 | {pos=(1.000000 2.000000), neighbors=(2 4), ID=3, clips=( )}, 93 | {pos=(0.000000 2.000000), neighbors=(3 0), ID=4, clips=( )}, 94 | {pos=(2.500000 0.000000), neighbors=(0 6), ID=5, clips=( 10 )}, 95 | {pos=(3.500000 2.000000), neighbors=(5 1), ID=6, clips=( 10 )}] 96 | 97 | Note the two new vertices (5 & 6) created by the clip plane have the ID of the plane that created them (10) listed in their ``clips`` parameter. It is not required to construct Planes with unique ID's like this, in which case all Planes have the same ID and this ``clips`` parameter is less useful. 98 | 99 | If we instead clip the original Polygon by the plane with the unit normal flipped in the opposite direction we get the other part of the Polygon:: 100 | 101 | planes = [Plane2d(Vector2d(3, 1), Vector2d(1, -0.5).unitVector(), 20)] 102 | clipPolygon(poly, planes) 103 | print "Reverse clip: ", list(poly) 104 | 105 | .. image:: notched_polygon_clip2.* 106 | :width: 400 107 | :alt: You should see a clipped polygon 108 | 109 | and vertices:: 110 | 111 | Reverse clip: [{pos=(4.000000 0.000000), neighbors=(2 1), ID=0, clips=( )}, 112 | {pos=(4.000000 2.000000), neighbors=(0 3), ID=1, clips=( )}, 113 | {pos=(2.500000 0.000000), neighbors=(3 0), ID=2, clips=( 20 )}, 114 | {pos=(3.500000 2.000000), neighbors=(1 2), ID=3, clips=( 20 )}] 115 | 116 | Clipping by multiple planes 117 | ---------------------------------------- 118 | 119 | Similarly we can clip by multiple planes simultaneously:: 120 | 121 | planes = [Plane2d(Vector2d(3, 1), Vector2d(-1, 0.5).unitVector(), 10), 122 | Plane2d(Vector2d(2, 1.1), Vector2d(1, 5).unitVector(), 30)] 123 | clipPolygon(poly, planes) 124 | print "Double clip: ", list(poly) 125 | 126 | .. image:: notched_polygon_clip3.* 127 | :width: 400 128 | :alt: You should see a clipped polygon 129 | 130 | and the vertices are now:: 131 | 132 | Double clip: [{pos=(3.000000 2.000000), neighbors=(3 4), ID=0, clips=( )}, 133 | {pos=(1.000000 2.000000), neighbors=(5 2), ID=1, clips=( )}, 134 | {pos=(0.000000 2.000000), neighbors=(1 6), ID=2, clips=( )}, 135 | {pos=(3.500000 2.000000), neighbors=(7 0), ID=3, clips=( 10 )}, 136 | {pos=(2.083333 1.083333), neighbors=(0 7), ID=4, clips=( 30 )}, 137 | {pos=(1.875000 1.125000), neighbors=(6 1), ID=5, clips=( 30 )}, 138 | {pos=(0.000000 1.500000), neighbors=(2 5), ID=6, clips=( 30 )}, 139 | {pos=(2.954545 0.909091), neighbors=(4 3), ID=7, clips=( 10 30 )}] 140 | 141 | Note in this case we have created two independent loop of vertices in our resulting Polygon. 142 | 143 | 3D Polyhedral clipping 144 | ----------------------- 145 | 146 | The interface for clipping polyhedra in 3D is very similar to the 2D polygonal examples above. For instance, if we extrude the non-convex polygonal example in in the :math:`z` direction for our initial polyhedron using the following Python code:: 147 | 148 | notched_points = [Vector3d(*coords) 149 | for coords in [(0,0,0), (4,0,0), (4,2,0), (3,2,0), (2,1,0), (1,2,0), (0,2,0), 150 | (0,0,1), (4,0,1), (4,2,1), (3,2,1), (2,1,1), (1,2,1), (0,2,1)]] 151 | notched_neighbors = [[7, 6, 1], # 0 152 | [0, 2, 8], # 1 153 | [1, 3, 9], # 2 154 | [4, 10, 2], # 3 155 | [5, 11, 3], # 4 156 | [6, 12, 4], # 5 157 | [13, 5, 0], # 6 158 | [8, 13, 0], # 7 159 | [1, 9, 7], # 8 160 | [2, 10, 8], # 9 161 | [9, 3, 11], # 10 162 | [10, 4, 12], # 11 163 | [11, 5, 13], # 12 164 | [7, 12, 6]] # 13 165 | poly = Polyhedron() 166 | initializePolyhedron(poly, notched_points, notched_neighbors) 167 | 168 | we get the following polyhedron: 169 | 170 | .. image:: notched_polyhedron.* 171 | :width: 400 172 | :alt: You should see a clipped polyhedron 173 | 174 | We can clip this example with a pair of planes:: 175 | 176 | planes = [Plane3d(Vector3d(3, 1, 0), Vector3d(-1, 0.5, -1.5).unitVector(), 10), 177 | Plane3d(Vector3d(1, 1, 0), Vector3d(1, 0, -1).unitVector(), 30)] 178 | clipPolyhedron(poly, planes) 179 | print "Double clip: ", list(poly) 180 | 181 | yielding a shape of: 182 | 183 | .. image:: notched_polyhedron_clip3.* 184 | :width: 400 185 | :alt: You should see a clipped polyhedron 186 | 187 | and vertex output:: 188 | 189 | Double clip: [{pos=(3.000000 2.000000 0.000000), neighbors=( 1 4 5 ), ID=0, clips=( )}, 190 | {pos=(2.000000 1.000000 0.000000), neighbors=( 2 6 0 ), ID=1, clips=( )}, 191 | {pos=(1.000000 2.000000 0.000000), neighbors=( 7 8 1 ), ID=2, clips=( )}, 192 | {pos=(2.500000 0.000000 0.000000), neighbors=( 5 9 10 ), ID=3, clips=( 10 )}, 193 | {pos=(3.000000 2.000000 0.333333), neighbors=( 6 5 0 ), ID=4, clips=( 10 )}, 194 | {pos=(3.500000 2.000000 0.000000), neighbors=( 4 3 0 ), ID=5, clips=( 10 )}, 195 | {pos=(2.000000 1.000000 0.666667), neighbors=( 11 4 1 ), ID=6, clips=( 10 )}, 196 | {pos=(1.000000 2.000000 0.000000), neighbors=( 10 8 2 ), ID=7, clips=( 30 )}, 197 | {pos=(1.000000 2.000000 0.000000), neighbors=( 7 11 2 ), ID=8, clips=( 30 )}, 198 | {pos=(1.600000 0.000000 0.600000), neighbors=( 11 10 3 ), ID=9, clips=( 10 30 )}, 199 | {pos=(1.000000 0.000000 0.000000), neighbors=( 9 7 3 ), ID=10, clips=( 30 )}, 200 | {pos=(1.833333 1.166667 0.833333), neighbors=( 8 9 6 ), ID=11, clips=( 10 30 )}] 201 | -------------------------------------------------------------------------------- /src/polyclipper_serializeImpl.hh: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // Methods for serializing/dserializing PolyClipper types 3 | //------------------------------------------------------------------------------ 4 | #include "polyclipper_utilities.hh" 5 | 6 | namespace PolyClipper { 7 | 8 | namespace internal { 9 | 10 | //------------------------------------------------------------------------------ 11 | // Serialize a double 12 | //------------------------------------------------------------------------------ 13 | inline 14 | void 15 | serialize(const double val, 16 | std::string& buffer) { 17 | const auto n = sizeof(double); 18 | const char* data = reinterpret_cast(&val); 19 | std::copy(data, data + n, std::back_inserter(buffer)); 20 | } 21 | 22 | //------------------------------------------------------------------------------ 23 | // Serialize an int 24 | //------------------------------------------------------------------------------ 25 | inline 26 | void 27 | serialize(const int val, 28 | std::string& buffer) { 29 | const auto n = sizeof(int); 30 | const char* data = reinterpret_cast(&val); 31 | std::copy(data, data + n, std::back_inserter(buffer)); 32 | } 33 | 34 | //------------------------------------------------------------------------------ 35 | // Serialize a size_t 36 | //------------------------------------------------------------------------------ 37 | inline 38 | void 39 | serialize(const size_t val, 40 | std::string& buffer) { 41 | const auto n = sizeof(size_t); 42 | const char* data = reinterpret_cast(&val); 43 | std::copy(data, data + n, std::back_inserter(buffer)); 44 | } 45 | 46 | //------------------------------------------------------------------------------ 47 | // Serialize a std::string 48 | //------------------------------------------------------------------------------ 49 | inline 50 | void 51 | serialize(const std::string& val, 52 | std::string& buffer) { 53 | const auto n = val.size(); 54 | serialize(n, buffer); 55 | std::copy(val.begin(), val.end(), std::back_inserter(buffer)); 56 | } 57 | 58 | //------------------------------------------------------------------------------ 59 | // Serialize a Vector 60 | //------------------------------------------------------------------------------ 61 | template 62 | inline 63 | void 64 | serialize(const typename VA::VECTOR& val, 65 | std::string& buffer) { 66 | const auto triple_vals = VA::get_triple(val); 67 | serialize(triple_vals[0], buffer); 68 | serialize(triple_vals[1], buffer); 69 | serialize(triple_vals[2], buffer); 70 | } 71 | 72 | //------------------------------------------------------------------------------ 73 | // Serialize a Vertex2d 74 | //------------------------------------------------------------------------------ 75 | template 76 | inline 77 | void 78 | serialize(const Vertex2d& val, 79 | std::string& buffer) { 80 | serialize(val.position, buffer); 81 | serialize(val.neighbors.first, buffer); 82 | serialize(val.neighbors.second, buffer); 83 | serialize(val.comp, buffer); 84 | serialize(val.ID, buffer); 85 | serialize(val.clips.size(), buffer); 86 | for (const auto x: val.clips) serialize(x, buffer); 87 | } 88 | 89 | //------------------------------------------------------------------------------ 90 | // Serialize a Vertex3d 91 | //------------------------------------------------------------------------------ 92 | template 93 | inline 94 | void 95 | serialize(const Vertex3d& val, 96 | std::string& buffer) { 97 | serialize(val.position, buffer); 98 | serialize(val.neighbors.size(), buffer); 99 | for (const auto x: val.neighbors) serialize(x, buffer); 100 | serialize(val.comp, buffer); 101 | serialize(val.ID, buffer); 102 | serialize(val.clips.size(), buffer); 103 | for (const auto x: val.clips) serialize(x, buffer); 104 | } 105 | 106 | //------------------------------------------------------------------------------ 107 | // Serialize a polygon 108 | //------------------------------------------------------------------------------ 109 | template 110 | inline 111 | void 112 | serialize(const std::vector>& val, 113 | std::string& buffer) { 114 | size_t n = val.size(); 115 | serialize(n, buffer); 116 | for (const auto& x: val) serialize(x, buffer); 117 | } 118 | 119 | //------------------------------------------------------------------------------ 120 | // Serialize a polyhedron 121 | //------------------------------------------------------------------------------ 122 | template 123 | inline 124 | void 125 | serialize(const std::vector>& val, 126 | std::string& buffer) { 127 | serialize(val.size(), buffer); 128 | for (const auto& x: val) serialize(x, buffer); 129 | } 130 | 131 | //------------------------------------------------------------------------------ 132 | // Serialize a plane 133 | //------------------------------------------------------------------------------ 134 | template 135 | inline 136 | void 137 | serialize(const Plane& val, 138 | std::string& buffer) { 139 | serialize(val.dist, buffer); 140 | serialize(val.normal, buffer); 141 | serialize(val.ID, buffer); 142 | } 143 | 144 | //------------------------------------------------------------------------------ 145 | // Serialize std::vector 146 | //------------------------------------------------------------------------------ 147 | template 148 | inline 149 | void 150 | serialize(const std::vector>& val, 151 | std::string& buffer) { 152 | serialize(val.size(), buffer); 153 | for (const auto& x: val) serialize(x, buffer); 154 | } 155 | 156 | //------------------------------------------------------------------------------ 157 | // Deserialize a double 158 | //------------------------------------------------------------------------------ 159 | inline 160 | void 161 | deserialize(double& val, 162 | std::string::const_iterator& itr, 163 | const std::string::const_iterator& endBuffer) { 164 | const auto n = sizeof(double); 165 | char* data = reinterpret_cast(&val); 166 | std::copy(itr, itr + n, data); 167 | itr += n; 168 | PCASSERT(itr <= endBuffer); 169 | } 170 | 171 | //------------------------------------------------------------------------------ 172 | // Deserialize an int 173 | //------------------------------------------------------------------------------ 174 | inline 175 | void 176 | deserialize(int& val, 177 | std::string::const_iterator& itr, 178 | const std::string::const_iterator& endBuffer) { 179 | const auto n = sizeof(int); 180 | char* data = reinterpret_cast(&val); 181 | std::copy(itr, itr + n, data); 182 | itr += n; 183 | PCASSERT(itr <= endBuffer); 184 | } 185 | 186 | //------------------------------------------------------------------------------ 187 | // Deserialize a size_t 188 | //------------------------------------------------------------------------------ 189 | inline 190 | void 191 | deserialize(size_t& val, 192 | std::string::const_iterator& itr, 193 | const std::string::const_iterator& endBuffer) { 194 | const auto n = sizeof(size_t); 195 | char* data = reinterpret_cast(&val); 196 | std::copy(itr, itr + n, data); 197 | itr += n; 198 | PCASSERT(itr <= endBuffer); 199 | } 200 | 201 | //------------------------------------------------------------------------------ 202 | // Deserialize a std::string 203 | //------------------------------------------------------------------------------ 204 | inline 205 | void 206 | deserialize(std::string& val, 207 | std::string::const_iterator& itr, 208 | const std::string::const_iterator& endBuffer) { 209 | size_t n; 210 | deserialize(n, itr, endBuffer); 211 | val.resize(n); 212 | std::copy(itr, itr+n, val.begin()); 213 | itr += n; 214 | PCASSERT(itr <= endBuffer); 215 | } 216 | 217 | //------------------------------------------------------------------------------ 218 | // Deserialize a Vector 219 | //------------------------------------------------------------------------------ 220 | template 221 | inline 222 | void 223 | deserialize(typename VA::VECTOR& val, 224 | std::string::const_iterator& itr, 225 | const std::string::const_iterator& endBuffer) { 226 | std::array triple_vals; 227 | deserialize(triple_vals[0],itr, endBuffer); 228 | deserialize(triple_vals[1],itr, endBuffer); 229 | deserialize(triple_vals[2],itr, endBuffer); 230 | VA::set_triple(val, triple_vals); 231 | } 232 | 233 | //------------------------------------------------------------------------------ 234 | // Deserialize a Vertex2d 235 | //------------------------------------------------------------------------------ 236 | template 237 | inline 238 | void 239 | deserialize(Vertex2d& val, 240 | std::string::const_iterator& itr, 241 | const std::string::const_iterator& endBuffer) { 242 | size_t n; 243 | int x; 244 | deserialize(val.position, itr, endBuffer); 245 | deserialize(val.neighbors.first, itr, endBuffer); 246 | deserialize(val.neighbors.second, itr, endBuffer); 247 | deserialize(val.comp, itr, endBuffer); 248 | deserialize(val.ID, itr, endBuffer); 249 | deserialize(n, itr, endBuffer); 250 | val.clips.clear(); 251 | for (auto i = 0; i < n; ++i) { 252 | deserialize(x, itr, endBuffer); 253 | val.clips.insert(x); 254 | } 255 | } 256 | 257 | //------------------------------------------------------------------------------ 258 | // Deserialize a Vertex3d 259 | //------------------------------------------------------------------------------ 260 | template 261 | inline 262 | void 263 | deserialize(Vertex3d& val, 264 | std::string::const_iterator& itr, 265 | const std::string::const_iterator& endBuffer) { 266 | size_t n; 267 | int x; 268 | deserialize(val.position, itr, endBuffer); 269 | deserialize(n, itr, endBuffer); 270 | val.neighbors.resize(n); 271 | for (auto i = 0; i < n; ++i) deserialize(val.neighbors[i], itr, endBuffer); 272 | deserialize(val.comp, itr, endBuffer); 273 | deserialize(val.ID, itr, endBuffer); 274 | deserialize(n, itr, endBuffer); 275 | val.clips.clear(); 276 | for (auto i = 0; i < n; ++i) { 277 | deserialize(x, itr, endBuffer); 278 | val.clips.insert(x); 279 | } 280 | } 281 | 282 | //------------------------------------------------------------------------------ 283 | // Deserialize a polygon 284 | //------------------------------------------------------------------------------ 285 | template 286 | inline 287 | void 288 | deserialize(std::vector>& val, 289 | std::string::const_iterator& itr, 290 | const std::string::const_iterator& endBuffer) { 291 | val.clear(); 292 | size_t n; 293 | Vertex2d x; 294 | deserialize(n, itr, endBuffer); 295 | for (auto i = 0; i < n; ++i) { 296 | deserialize(x, itr, endBuffer); 297 | val.push_back(x); 298 | } 299 | } 300 | 301 | //------------------------------------------------------------------------------ 302 | // Deserialize a polyhedron 303 | //------------------------------------------------------------------------------ 304 | template 305 | inline 306 | void 307 | deserialize(std::vector>& val, 308 | std::string::const_iterator& itr, 309 | const std::string::const_iterator& endBuffer) { 310 | val.clear(); 311 | size_t n; 312 | Vertex3d x; 313 | deserialize(n, itr, endBuffer); 314 | for (auto i = 0; i < n; ++i) { 315 | deserialize(x, itr, endBuffer); 316 | val.push_back(x); 317 | } 318 | } 319 | 320 | //------------------------------------------------------------------------------ 321 | // Deserialize a plane 322 | //------------------------------------------------------------------------------ 323 | template 324 | inline 325 | void 326 | deserialize(Plane& val, 327 | std::string::const_iterator& itr, 328 | const std::string::const_iterator& endBuffer) { 329 | deserialize(val.dist, itr, endBuffer); 330 | deserialize(val.normal, itr, endBuffer); 331 | deserialize(val.ID, itr, endBuffer); 332 | } 333 | 334 | //------------------------------------------------------------------------------ 335 | // Deserialize std::vector 336 | //------------------------------------------------------------------------------ 337 | template 338 | inline 339 | void 340 | deserialize(std::vector>& val, 341 | std::string::const_iterator& itr, 342 | const std::string::const_iterator& endBuffer) { 343 | val.clear(); 344 | size_t n; 345 | Plane x; 346 | deserialize(n, itr, endBuffer); 347 | for (auto i = 0; i < n; ++i) { 348 | deserialize(x, itr, endBuffer); 349 | val.push_back(x); 350 | } 351 | } 352 | 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /docs/datatypes.rst: -------------------------------------------------------------------------------- 1 | ######################################## 2 | PolyClipper data types 3 | ######################################## 4 | 5 | As mentioned in section :ref:`PolyClipper concepts`, PolyClipper includes the data types ``Vector2d``, ``Vector3d``, ``Plane2d``, ``Plane3d``, ``Vertex2d``, and ``Vertex3d``, all defined inside the C++ namespace ``PolyClipper``. For 2D (polygon) operations include the header ``polyclipper2d.hh``, while for 3D (polyhedron) include ``polyclipper3d.hh``. 6 | In this section we describe the C++ interface for each of these types. 7 | 8 | .. note:: 9 | Python interface notes 10 | 11 | The Python interface is identical to C++, with template parameters defaulted to PolyClippper's Vector2d/Vector3d implementations. We also define a ``Polygon`` and ``Polyhedron`` class for in Python, which are ``std::vector>`` and ``std::vector>`` under the hood. 12 | 13 | .. 14 | Vector2d 15 | ---------- 16 | 17 | .. cpp:namespace:: PolyClipper 18 | 19 | Vector classes 20 | -------------------- 21 | 22 | Vector2d 23 | ~~~~~~~~ 24 | 25 | .. cpp:class:: Vector2d 26 | 27 | Vector2d represents a 2D vector coordinate in space, :math:`(x,y)`. It supports a small number of simple vector manipulation operations: 28 | 29 | .. cpp:member:: double Vector2d::x 30 | 31 | :math:`x` coordinate 32 | 33 | .. cpp:member:: double Vector2d::y 34 | 35 | :math:`y` coordinate 36 | 37 | .. cpp:function:: Vector2d::Vector2d() 38 | 39 | Construct a ``Vector2d(0,0)`` 40 | 41 | .. cpp:function:: Vector2d::Vector2d(double X, double Y) 42 | 43 | Construct a ``Vector2d(X,Y)`` 44 | 45 | .. cpp:function:: double Vector2d::dot(const Vector2d& b) const 46 | 47 | Returns the dot product: :math:`\vec{a} \cdot \vec{b}` 48 | 49 | .. cpp:function:: double Vector2d::cross(const Vector2d& b) const 50 | 51 | Returns the :math:`z` component of the cross product: :math:`\vec{a} \times \vec{b}` 52 | 53 | .. cpp:function:: double Vector2d::magnitude2() const 54 | 55 | Returns the square of the magnitude of the vector: :math:`\vec{a} \cdot \vec{a}` 56 | 57 | .. cpp:function:: double Vector2d::magnitude() const 58 | 59 | Returns the magnitude of the vector: :math:`\sqrt{\vec{a} \cdot \vec{a}}` 60 | 61 | .. cpp:function:: Vector2d& Vector2d::operator*=(const double b) 62 | 63 | Inplace multiplication by a scalar: :math:`\vec{a} = b \vec{a}` 64 | 65 | .. cpp:function:: Vector2d& Vector2d::operator/=(const double b) 66 | 67 | Inplace division by a scalar: :math:`\vec{a} = \vec{a}/b` 68 | 69 | .. cpp:function:: Vector2d& Vector2d::operator+=(const Vector2d& b) 70 | 71 | Inplace addition with another vector: :math:`\vec{a} = \vec{a} + \vec{b}` 72 | 73 | .. cpp:function:: Vector2d& Vector2d::operator-=(const Vector2d& b) 74 | 75 | Inplace subtraction with another vector: :math:`\vec{a} = \vec{a} - \vec{b}` 76 | 77 | .. cpp:function:: Vector2d Vector2d::operator*(const double b) const 78 | 79 | Returns the result of multiplication by a scalar: :math:`b \vec{a}` 80 | 81 | .. cpp:function:: Vector2d Vector2d::operator/(const double b) const 82 | 83 | Returns the result of division by a scalar: :math:`\vec{a}/b` 84 | 85 | .. cpp:function:: Vector2d Vector2d::operator+(const Vector2d& b) const 86 | 87 | Returns the result of addition with another vector: :math:`\vec{a} + \vec{b}` 88 | 89 | .. cpp:function:: Vector2d Vector2d::operator-(const Vector2d& b) const 90 | 91 | Returns the result of subtraction with another vector: :math:`\vec{a} - \vec{b}` 92 | 93 | .. cpp:function:: Vector2d Vector2d::operator-() const 94 | 95 | Returns the negative of this vector: :math:`-\vec{a}` 96 | 97 | .. cpp:function:: Vector2d Vector2d::unitVector() const 98 | 99 | Returns the unit vector pointing in the direction of this one: :math:`\vec{a}/\sqrt{\vec{a} \cdot \vec{a}}`. 100 | 101 | If :math:`\vec{a} = (0,0)`, returns the unit vector in the :math:`x` direction: :math:`(1,0)`. 102 | 103 | Vector3d 104 | ~~~~~~~~ 105 | 106 | .. cpp:class:: Vector3d 107 | 108 | Vector3d represents a 3D vector coordinate in space, :math:`(x,y,z)`. It supports a small number of simple vector manipulation operations: 109 | 110 | .. cpp:member:: double Vector3d::x 111 | 112 | :math:`x` coordinate 113 | 114 | .. cpp:member:: double Vector3d::y 115 | 116 | :math:`y` coordinate 117 | 118 | .. cpp:member:: double Vector3d::z 119 | 120 | :math:`z` coordinate 121 | 122 | .. cpp:function:: Vector3d::Vector3d() 123 | 124 | Construct a ``Vector3d(0,0,0)`` 125 | 126 | .. cpp:function:: Vector3d::Vector3d(double X, double Y, double Z) 127 | 128 | Construct a ``Vector3d(X,Y,Z)`` 129 | 130 | .. cpp:function:: double Vector3d::dot(const Vector3d& b) const 131 | 132 | Returns the dot product: :math:`\vec{a} \cdot \vec{b}` 133 | 134 | .. cpp:function:: Vector3d Vector3d::cross(const Vector3d& b) const 135 | 136 | Returns the cross product: :math:`\vec{a} \times \vec{b}` 137 | 138 | .. cpp:function:: double Vector3d::magnitude2() const 139 | 140 | Returns the square of the magnitude of the vector: :math:`\vec{a} \cdot \vec{a}` 141 | 142 | .. cpp:function:: double Vector3d::magnitude() const 143 | 144 | Returns the magnitude of the vector: :math:`\sqrt{\vec{a} \cdot \vec{a}}` 145 | 146 | .. cpp:function:: Vector3d& Vector3d::operator*=(const double b) 147 | 148 | Inplace multiplication by a scalar: :math:`\vec{a} = b \vec{a}` 149 | 150 | .. cpp:function:: Vector3d& Vector3d::operator/=(const double b) 151 | 152 | Inplace division by a scalar: :math:`\vec{a} = \vec{a}/b` 153 | 154 | .. cpp:function:: Vector3d& Vector3d::operator+=(const Vector3d& b) 155 | 156 | Inplace addition with another vector: :math:`\vec{a} = \vec{a} + \vec{b}` 157 | 158 | .. cpp:function:: Vector3d& Vector3d::operator-=(const Vector3d& b) 159 | 160 | Inplace subtraction with another vector: :math:`\vec{a} = \vec{a} - \vec{b}` 161 | 162 | .. cpp:function:: Vector3d Vector3d::operator*(const double b) const 163 | 164 | Returns the result of multiplication by a scalar: :math:`b \vec{a}` 165 | 166 | .. cpp:function:: Vector3d Vector3d::operator/(const double b) const 167 | 168 | Returns the result of division by a scalar: :math:`\vec{a}/b` 169 | 170 | .. cpp:function:: Vector3d Vector3d::operator+(const Vector3d& b) const 171 | 172 | Returns the result of addition with another vector: :math:`\vec{a} + \vec{b}` 173 | 174 | .. cpp:function:: Vector3d Vector3d::operator-(const Vector3d& b) const 175 | 176 | Returns the result of subtraction with another vector: :math:`\vec{a} - \vec{b}` 177 | 178 | .. cpp:function:: Vector3d Vector3d::operator-() const 179 | 180 | Returns the negative of this vector: :math:`-\vec{a}` 181 | 182 | .. cpp:function:: Vector3d Vector3d::unitVector() const 183 | 184 | Returns the unit vector pointing in the direction of this one: :math:`\vec{a}/\sqrt{\vec{a} \cdot \vec{a}}`. 185 | 186 | If :math:`\vec{a} = (0,0,0)`, returns the unit vector in the :math:`x` direction: :math:`(1,0,0)`. 187 | 188 | Plane classes 189 | -------------------- 190 | 191 | .. cpp:class:: template Plane 192 | 193 | Plane represents a plane for clipping polytopes, and is templated on ``VA`` (a Vector adapter) type describing how to use the appropriate geometric Vector type. 194 | 195 | .. note:: 196 | In the headers ``polyclipper2d.hh`` and ``polyclipper3d.hh`` we define the typedefs:: 197 | 198 | using Plane2d = Plane>; 199 | using Plane3d = Plane>; 200 | 201 | (both in the namespace ``PolyClipper``) for convenience when working with PolyClipper's native Vectors. 202 | 203 | A plane is stored as a unit normal and closest signed distance from the plane to the origin: :math:`(\hat{n}, d)`. The signed distance from the plane to any point :math:`\vec{p}` is 204 | 205 | .. math:: 206 | d_s(\vec{p}) = (\vec{p} - \vec{p}_0) \cdot \hat{n} = d + \vec{p} \cdot \hat{n}, 207 | 208 | where :math:`\vec{p}_0` is any point in the plane. Note with this definition the :math:`d` parameter defining the plane is :math:`d = -\vec{p}_0 \cdot \hat{n}`. 209 | 210 | .. cpp:type:: Vector VA::VECTOR 211 | 212 | .. cpp:member:: double Plane::dist 213 | 214 | The minimum signed distance from the origin to the plane :math:`d`. 215 | 216 | .. cpp:member:: Vector Plane::normal 217 | 218 | The unit normal to the plane :math:`\hat{n}`. 219 | 220 | .. cpp:member:: int Plane::ID 221 | 222 | An optional integer identification number for the plane. This is used by ``Vertex`` to record which plane(s) are responsible for creating the vertex. 223 | 224 | .. cpp:function:: Plane::Plane() 225 | 226 | Default constructor -- implies {:math:`\hat{n}, d`, ID} = {(1,0,0), 0.0, std::numeric_limits::min()} 227 | 228 | .. cpp:function:: Plane::Plane(const double d, const Vector& nhat) 229 | 230 | Construct with {:math:`\hat{n}, d`, ID} = {nhat, d, std::numeric_limits::min()} 231 | 232 | .. cpp:function:: Plane::Plane(const Vector& p, const Vector& nhat) 233 | 234 | Construct specifying the normal and a point in the plane, so {:math:`\hat{n}, d`, ID} = {nhat, :math:`-p\cdot\hat{n}`, std::numeric_limits::min()} 235 | 236 | .. cpp:function:: Plane::Plane(const Vector& p, const Vector& nhat, const int id) 237 | 238 | Construct specifying the normal, a point in the plane, and ID, so {:math:`\hat{n}, d`, ID} = {nhat, :math:`-p\cdot\hat{n}`, id} 239 | 240 | Vertex classes 241 | -------------------- 242 | 243 | Vertex2d 244 | ~~~~~~~~ 245 | 246 | .. cpp:class:: template> Vertex2d 247 | 248 | Vertex2d is used to encode Polygons in 2d. A vertex includes a position and the connectivity to neighboring vertices in the Polygon. In this 2d case, the connectivity is always 2 vertices, ordered such that going from the first neighbor, to this vertex, and on to the last neighbor goes around the Polygon in the counter-clockwise direction. This is illustrated in the Polygon examples in :ref:`PolyClipper concepts`. 249 | 250 | .. note:: 251 | Note that while ``Vertex2d`` is templated on a geometric Vector trait class, the template argument defaults to an implementation appropriate for use with ``PolyClipper::Vector2d``. 252 | 253 | .. cpp:type:: Vector VA::VECTOR 254 | 255 | .. cpp:member:: Vector Vertex2d::position 256 | 257 | The position of the vertex in :math:`(x,y)` coordinates. 258 | 259 | .. cpp:member:: std::pair Vertex2d::neighbors 260 | 261 | The neighbor vertices this vertex is connected too. These should be listed in counter-clockwise order going around the Polygon, so that ``neighbors.first`` is clockwise and ``neighbors.second`` is counter-clockwise from this vertex. 262 | 263 | .. cpp:member:: int Vertex2d::comp 264 | 265 | An internal state integer, for comparing this vertex to planes. Used and overwritten during clipping operations. 266 | 267 | .. cpp:member:: int Vertex2d::ID 268 | 269 | An optional ID index for this vertex. Used and overwritten during clipping operations. 270 | 271 | .. cpp:member:: std::set Vertex2d::clips 272 | 273 | The set of Plane2d ID's responsible for creating this vertex during clipping operations. Used and overwritten during clipping operations. 274 | 275 | .. cpp:function:: Vertex2d::Vertex2d() 276 | 277 | Default constructor, sets member data to {position, neighbors, comp, ID, clips} = {(0,0), (), 1, -1, {}} 278 | 279 | .. cpp:function:: Vertex2d::Vertex2d(const Vector& pos) 280 | 281 | Construct with just the position 282 | 283 | .. cpp:function:: Vertex2d::Vertex2d(const Vector& pos, const int c) 284 | 285 | Construct with {position, comp} = {pos, c} 286 | 287 | Vertex3d 288 | ~~~~~~~~ 289 | 290 | .. cpp:class:: template> Vertex3d 291 | 292 | Vertex3d is used to encode Polyhedra in 3d. A vertex includes a position and the connectivity to neighboring vertices in the Polyhedron. For Polyhedra, the neighbor connectivity should be 3 or more neighbors, listed counter-clockwise as viewed from the exterior side of the vertex (see the illustrations in :ref:`PolyClipper concepts` for examples). 293 | 294 | .. note:: 295 | Note that while ``Vertex3d`` is templated on a geometric Vector trait class, the template argument defaults to an implementation appropriate for use with ``PolyClipper::Vector3d``. 296 | 297 | .. cpp:type:: Vector VA::VECTOR 298 | 299 | .. cpp:member:: Vector Vertex3d::position 300 | 301 | The position of the vertex in :math:`(x,y,z)` coordinates. 302 | 303 | .. cpp:member:: std::vector Vertex3d::neighbors 304 | 305 | The neighbor vertices this vertex is connected too, listed in counter-clockwise order as viewed from the exterior of the Polyhedron. 306 | 307 | .. cpp:member:: int Vertex3d::comp 308 | 309 | An internal state integer, for comparing this vertex to planes. Used and overwritten during clipping operations. 310 | 311 | .. cpp:member:: int Vertex3d::ID 312 | 313 | An optional ID index for this vertex. Used and overwritten during clipping operations. 314 | 315 | .. cpp:member:: std::set Vertex3d::clips 316 | 317 | The set of Plane3d ID's responsible for creating this vertex during clipping operations. Used and overwritten during clipping operations. 318 | 319 | .. cpp:function:: Vertex3d::Vertex3d() 320 | 321 | Default constructor, sets member data to {position, neighbors, comp, ID, clips} = {(0,0,0), (), 1, -1, {}} 322 | 323 | .. cpp:function:: Vertex3d::Vertex3d(const Vector& pos) 324 | 325 | Construct with just the position 326 | 327 | .. cpp:function:: Vertex3d::Vertex3d(const Vector& pos, const int c) 328 | 329 | Construct with {position, comp} = {pos, c} 330 | -------------------------------------------------------------------------------- /docs/custom_vectors.rst: -------------------------------------------------------------------------------- 1 | ################################################## 2 | Using PolyClipper with a user defined Vector type 3 | ################################################## 4 | 5 | The examples shown in :ref:`Polyclipper concepts` all use PolyClipper's native Python interface, which employs PolyClipper's native (``Vector2d`` and ``Vector3d``) classes. In C++ however the PolyClipper functions are all templated on a trait class describing how to invoke the assorted Vector operations (such as dot and cross products), allowing the C++ user to use PolyClipper methods directly with their own geometric Vector types. The header file ``polyclipper_adapter.hh`` shows how the appropriate trait class for using the native PolyClipper Vector's is defined. In this section we will describe how to write a trait class to adapt your own C++ Vector's, as well as show concrete examples adapting the STL class ``std::array`` for use as geometric Vectors in PolyClipper. 6 | 7 | Writing a trait class for adapting a Vector with PolyClipper 8 | ------------------------------------------------------------ 9 | 10 | The following code is an example of all the methods we need to define in a trait class to adapt any C++ geometric Vector type for use with PolyClipper. Depending on the user Vector type, the traited methods below may be simple wrappers around the corresponding methods of the users Vector type, or may entirely define the appropriate calculations. 11 | 12 | .. code-block:: c++ 13 | 14 | struct VectorAdapter { 15 | using VECTOR = // Fill in your Vector type here 16 | static VECTOR Vector(double a, double b) // only 2D : construct and return a 2D Vector 17 | static VECTOR Vector(double a, double b, double c) // only 3D : construct and return a 3D Vector 18 | static bool equal(const VECTOR& a, const VECTOR& b) // test if a == b 19 | static double& x(VECTOR& a) // return the x component 20 | static double& y(VECTOR& a) // return the y component 21 | static double& z(VECTOR& a) // only 3D : return the z component 22 | static double x(const VECTOR& a) // return the x component (const Vector) 23 | static double y(const VECTOR& a) // return the y component (const Vector) 24 | static double z(const VECTOR& a) // only 3D : return the z component (const Vector) 25 | static double dot(const VECTOR& a, const VECTOR& b) // return the dot product of a and b 26 | static double crossmag(const VECTOR& a, const VECTOR& b) // only 2D : return the z component of the cross product of a and b 27 | static VECTOR cross(const VECTOR& a, const VECTOR& b) // only 3D : return the cross product of a and b 28 | static double magnitude2(const VECTOR& a) // return the square of the magnitude 29 | static double magnitude(const VECTOR& a) // return the magnitude 30 | static VECTOR& imul(VECTOR& a, const double b) // in place multiplication by a scalar 31 | static VECTOR& idiv(VECTOR& a, const double b) // in place division by a scalar 32 | static VECTOR& iadd(VECTOR& a, const VECTOR& b) // in place addition of Vectors a and b 33 | static VECTOR& isub(VECTOR& a, const VECTOR& b) // in place subtraction of Vectors a and b 34 | static VECTOR mul(const VECTOR& a, const double b) // return the multiplication of Vector a by scalar b 35 | static VECTOR div(const VECTOR& a, const double b) // return the division of Vector a by scalar b 36 | static VECTOR add(const VECTOR& a, const VECTOR& b) // return the sum of a + b 37 | static VECTOR sub(const VECTOR& a, const VECTOR& b) // return the difference a - b 38 | static VECTOR neg(const VECTOR& a) // return -a 39 | static VECTOR unitVector(const VECTOR& a) // return a unit vector in the direction of a 40 | static std::string str(const VECTOR& a) // return a string representation of a 41 | static std::array get_triple(const VECTOR& a) // returns a triple with the {x,y,z} values in the Vector (2D ignores 3rd component) 42 | static void set_triple(VECTOR& a, // set the Vector based on a triple of {x,y,z} values (2D ignores 3rd component) 43 | const std::array vals) 44 | }; 45 | 46 | .. note:: 47 | These methods need only be provided for 2D vectors:: 48 | 49 | VECTOR VectorAdapter::Vector(double a, double b) 50 | double VectorAdapter::crossmag(const VECTOR& a, const VECTOR& b) 51 | 52 | .. note:: 53 | These methods need only be provided for 3D vectors:: 54 | 55 | VECTOR VectorAdapter::Vector(double a, double b, double c) 56 | VECTOR VectorAdapter::cross(const VECTOR& a, const VECTOR& b) 57 | double& VectorAdapter::z(VECTOR& a) 58 | double VectorAdapter::z(const VECTOR& a) 59 | 60 | .. note:: 61 | The methods ``get_triple`` and ``set_triple`` should get/set three doubles ``{x,y,z}``, even in 2D. In the 2D case the third double value can be anything in these methods -- PolyClipper will ignore it. 62 | 63 | Once you have your trait class for adapting your C++ Vector ready, you can call PolyClipper functions with this class as the template parameter. For instance, in 3D the method for clipping a polyhedron is templated with a default for PolyClipper's internal Vector type:: 64 | 65 | template> 66 | void clipPolyhedron(std::vector>& poly, 67 | const std::vector>& planes); 68 | 69 | We can use our own templated Vector adapter (in this case called ``VectorAdapater``) by passing it like so:: 70 | 71 | std::vector> poly; 72 | std::vector> planes; 73 | // Fill in poly and planes at this point... 74 | clipPolyhedron(poly, planes); 75 | 76 | 77 | A 2D example using ``std::array`` as a Vector 78 | -------------------------------------------------------- 79 | 80 | In the test directory of PolyClipper (under ``test/test_array_vector``) is a complete example creating and clipping a polygon using ``std::array`` as the Vector type. The source looks like the following:: 81 | 82 | #include "polyclipper2d.hh" 83 | 84 | #include 85 | #include 86 | 87 | // Define a trait class for using a simple C style array of doubles as 2D Vector type for use in PolyClipper. 88 | struct ArrayAdapter2d { 89 | using VECTOR = std::array; 90 | static VECTOR Vector(double a, double b) { return {a, b}; } // only 2D 91 | static bool equal(const VECTOR& a, const VECTOR& b) { return (a[0] == b[0]) and (a[1] == b[1]); } 92 | static double& x(VECTOR& a) { return a[0]; } 93 | static double& y(VECTOR& a) { return a[1]; } 94 | static double x(const VECTOR& a) { return a[0]; } 95 | static double y(const VECTOR& a) { return a[1]; } 96 | static double dot(const VECTOR& a, const VECTOR& b) { return a[0]*b[0] + a[1]*b[1]; } 97 | static double crossmag(const VECTOR& a, const VECTOR& b) { return a[0]*b[1] - a[1]*b[0]; } // only 2D 98 | static double magnitude2(const VECTOR& a) { return a[0]*a[0] + a[1]*a[1]; } 99 | static double magnitude(const VECTOR& a) { return std::sqrt(magnitude2(a)); } 100 | static VECTOR& imul(VECTOR& a, const double b) { a[0] *= b; a[1] *= b; return a; } 101 | static VECTOR& idiv(VECTOR& a, const double b) { a[0] /= b; a[1] /= b; return a; } 102 | static VECTOR& iadd(VECTOR& a, const VECTOR& b) { a[0] += b[0]; a[1] += b[1]; return a; } 103 | static VECTOR& isub(VECTOR& a, const VECTOR& b) { a[0] -= b[0]; a[1] -= b[1]; return a; } 104 | static VECTOR mul(const VECTOR& a, const double b) { return Vector(a[0] * b, a[1] * b); } 105 | static VECTOR div(const VECTOR& a, const double b) { return Vector(a[0] / b, a[1] / b); } 106 | static VECTOR add(const VECTOR& a, const VECTOR& b) { return Vector(a[0] + b[0], a[1] + b[1]); } 107 | static VECTOR sub(const VECTOR& a, const VECTOR& b) { return Vector(a[0] - b[0], a[1] - b[1]); } 108 | static VECTOR neg(const VECTOR& a) { return Vector(-a[0], -a[1]); } 109 | static VECTOR unitVector(const VECTOR& a) { auto mag = magnitude(a); return mag > 1.0e-15 ? div(a, mag) : Vector(1.0, 0.0); } 110 | static std::string str(const VECTOR& a) { std::ostringstream os; os << "(" << a[0] << " " << a[1] << ")"; return os.str(); } 111 | static std::array get_triple(const VECTOR& a) { return {a[0], a[1], 0.0}; } 112 | static void set_triple(VECTOR& a, 113 | const std::array vals) { a = {vals[0], vals[1]}; } 114 | }; 115 | 116 | int main() { 117 | 118 | using VA = ArrayAdapter2d; 119 | using Vector = VA::VECTOR; 120 | using Polygon = std::vector>; 121 | using Plane = PolyClipper::Plane; 122 | 123 | // Make a square 124 | // 3 2 125 | // |----| 126 | // | | 127 | // |----| 128 | // 0 1 129 | const std::vector square_points = {VA::Vector(0.0,0.0), VA::Vector(10.0,0.0), VA::Vector(10.0,10.0), VA::Vector(0.0,10.0)}; 130 | const std::vector> square_neighbors = {{3, 1}, {0, 2}, {1, 3}, {2, 0}}; 131 | Polygon poly; 132 | double area; 133 | Vector cent; 134 | PolyClipper::initializePolygon(poly, square_points, square_neighbors); 135 | PolyClipper::moments(area, cent, poly); 136 | std::cout << "Initial polygon: " << polygon2string(poly) << std::endl 137 | << "Moments: " << area << " " << VA::str(cent) << std::endl << std::endl; 138 | 139 | // Clip by a couple of planes. 140 | const std::vector planes = {Plane(VA::Vector(0.2, 0.2), VA::unitVector(VA::Vector(1.0, 1.0)), 10), 141 | Plane(VA::Vector(0.2, 0.8), VA::unitVector(VA::Vector(0.5, -1.0)), 20)}; 142 | PolyClipper::clipPolygon(poly, planes); 143 | PolyClipper::moments(area, cent, poly); 144 | std::cout << "After clipping: " << polygon2string(poly) << std::endl 145 | << "Moments: " << area << " " << VA::str(cent) << std::endl; 146 | 147 | return 0; 148 | } 149 | 150 | 151 | .. note:: 152 | Note that the ``PolyClipper::Vector2d`` internal type defines its own versions of methods required by a Vector adapter trait class, so the adapter in that case (found in ``polyclipper_adapter.hh``) is only a thin wrapper around those methods. In this example, however, the ``std::array`` does not have any notion of mathematical concepts like the Vector dot product, cross product, magnitude, etc., so we explicitly compute those in the appropriate methods of ``ArrayAdapter2d`` above. 153 | 154 | Since in C++ PolyClipper is a header-only library, the above source will compile directly and yields the following output:: 155 | 156 | Initial polygon: { 157 | 0 (0 0) [3 1] clips[] 158 | 1 (10 0) [0 2] clips[] 159 | 2 (10 10) [1 3] clips[] 160 | 3 (0 10) [2 0] clips[] 161 | } 162 | 163 | Moments: 100 (5 5) 164 | 165 | After clipping: { 166 | 0 (10 0) [1 3] clips[] 167 | 1 (0.4 0) [2 0] clips[10 ] 168 | 2 (0 0.4) [4 1] clips[10 ] 169 | 3 (10 5.7) [0 4] clips[20 ] 170 | 4 (0 0.7) [3 2] clips[20 ] 171 | } 172 | 173 | Moments: 31.92 (6.31754 1.93001) 174 | 175 | A 3D example using ``std::array`` as a Vector 176 | -------------------------------------------------------- 177 | 178 | Similarly to above we can also use ``std::array`` as a Vector type in PolyClipper with an appropriate trait class:: 179 | 180 | #include "polyclipper3d.hh" 181 | 182 | #include 183 | #include 184 | 185 | // Define a trait class for using a simple C style array of doubles as 3D Vector type for use in PolyClipper. 186 | struct ArrayAdapter3d { 187 | using VECTOR = std::array; 188 | static VECTOR Vector(double a, double b, double c) { return {a, b, c}; } // only 3D 189 | static bool equal(const VECTOR& a, const VECTOR& b) { return (a[0] == b[0]) and (a[1] == b[1]) and (a[2] == b[2]); } 190 | static double& x(VECTOR& a) { return a[0]; } 191 | static double& y(VECTOR& a) { return a[1]; } 192 | static double& z(VECTOR& a) { return a[2]; } 193 | static double x(const VECTOR& a) { return a[0]; } 194 | static double y(const VECTOR& a) { return a[1]; } 195 | static double z(const VECTOR& a) { return a[2]; } 196 | static double dot(const VECTOR& a, const VECTOR& b) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; } 197 | static VECTOR cross(const VECTOR& a, const VECTOR& b) { return {a[1]*b[2] - a[2]*b[1], // only 3D 198 | a[2]*b[0] - a[0]*b[2], 199 | a[0]*b[1] - a[1]*b[0]}; } 200 | static double magnitude2(const VECTOR& a) { return a[0]*a[0] + a[1]*a[1] + a[2]*a[2]; } 201 | static double magnitude(const VECTOR& a) { return std::sqrt(magnitude2(a)); } 202 | static VECTOR& imul(VECTOR& a, const double b) { a[0] *= b; a[1] *= b; a[2] *= b; return a; } 203 | static VECTOR& idiv(VECTOR& a, const double b) { a[0] /= b; a[1] /= b; a[2] /= b; return a; } 204 | static VECTOR& iadd(VECTOR& a, const VECTOR& b) { a[0] += b[0]; a[1] += b[1]; a[2] += b[2]; return a; } 205 | static VECTOR& isub(VECTOR& a, const VECTOR& b) { a[0] -= b[0]; a[1] -= b[1]; a[2] -= b[2]; return a; } 206 | static VECTOR mul(const VECTOR& a, const double b) { return Vector(a[0] * b, a[1] * b, a[2] * b); } 207 | static VECTOR div(const VECTOR& a, const double b) { return Vector(a[0] / b, a[1] / b, a[2] / b); } 208 | static VECTOR add(const VECTOR& a, const VECTOR& b) { return Vector(a[0] + b[0], a[1] + b[1], a[2] + b[2]); } 209 | static VECTOR sub(const VECTOR& a, const VECTOR& b) { return Vector(a[0] - b[0], a[1] - b[1], a[2] - b[2]); } 210 | static VECTOR neg(const VECTOR& a) { return Vector(-a[0], -a[1], -a[2]); } 211 | static VECTOR unitVector(const VECTOR& a) { auto mag = magnitude(a); return mag > 1.0e-15 ? div(a, mag) : Vector(1.0, 0.0, 0.0); } 212 | static std::string str(const VECTOR& a) { std::ostringstream os; os << "(" << a[0] << " " << a[1] << " " << a[2] << ")"; return os.str(); } 213 | static std::array get_triple(const VECTOR& a) { return a; } 214 | static void set_triple(VECTOR& a, 215 | const std::array vals) { a = vals; } 216 | }; 217 | 218 | int main() { 219 | 220 | using VA = ArrayAdapter3d; 221 | using Vector = VA::VECTOR; 222 | using Polyhedron = std::vector>; 223 | using Plane = PolyClipper::Plane; 224 | 225 | // Make a cube |y 226 | // | 227 | // |____x 228 | // 3/-----/2 / 229 | // / /| /z 230 | // 7|-----|6| 231 | // | | | 232 | // | 0 | /1 233 | // |_____|/ 234 | // 4 5 235 | // 236 | const std::vector cube_points = {VA::Vector(0,0,0), VA::Vector(10,0,0), VA::Vector(10,10,0), VA::Vector(0,10,0), 237 | VA::Vector(0,0,10), VA::Vector(10,0,10), VA::Vector(10,10,10), VA::Vector(0,10,10)}; 238 | const std::vector> cube_neighbors = {{1, 4, 3}, 239 | {5, 0, 2}, 240 | {3, 6, 1}, 241 | {7, 2, 0}, 242 | {5, 7, 0}, 243 | {1, 6, 4}, 244 | {5, 2, 7}, 245 | {4, 6, 3}}; 246 | const std::vector> cube_facets = {{4, 5, 6, 7}, 247 | {1, 2, 6, 5}, 248 | {0, 3, 2, 1}, 249 | {4, 7, 3, 0}, 250 | {6, 2, 3, 7}, 251 | {1, 5, 4, 0}}; 252 | Polyhedron poly; 253 | double vol; 254 | Vector cent; 255 | PolyClipper::initializePolyhedron(poly, cube_points, cube_neighbors); 256 | PolyClipper::moments(vol, cent, poly); 257 | std::cout << "Initial polyhedron: " << polyhedron2string(poly) << std::endl 258 | << "Moments: " << vol << " " << VA::str(cent) << std::endl << std::endl; 259 | 260 | // Clip by a couple of planes. 261 | const std::vector planes = {Plane(VA::Vector(0.2, 0.2, 0.2), VA::unitVector(VA::Vector(1.0, 1.0, 1.0)), 10), 262 | Plane(VA::Vector(0.2, 0.8, 0.8), VA::unitVector(VA::Vector(0.5, -1.0, -1.0)), 20)}; 263 | PolyClipper::clipPolyhedron(poly, planes); 264 | PolyClipper::moments(vol, cent, poly); 265 | std::cout << "After clipping: " << polyhedron2string(poly) << std::endl 266 | << "Moments: " << vol << " " << VA::str(cent) << std::endl; 267 | 268 | return 0; 269 | } 270 | 271 | Executing this example produces the output:: 272 | 273 | Initial polyhedron: 0 ID=-1 comp=1 @ (0 0 0) neighbors=[1 4 3 ] clips[] 274 | 1 ID=-1 comp=1 @ (10 0 0) neighbors=[5 0 2 ] clips[] 275 | 2 ID=-1 comp=1 @ (10 10 0) neighbors=[3 6 1 ] clips[] 276 | 3 ID=-1 comp=1 @ (0 10 0) neighbors=[7 2 0 ] clips[] 277 | 4 ID=-1 comp=1 @ (0 0 10) neighbors=[5 7 0 ] clips[] 278 | 5 ID=-1 comp=1 @ (10 0 10) neighbors=[1 6 4 ] clips[] 279 | 6 ID=-1 comp=1 @ (10 10 10) neighbors=[5 2 7 ] clips[] 280 | 7 ID=-1 comp=1 @ (0 10 10) neighbors=[4 6 3 ] clips[] 281 | 282 | Moments: 1000 (5 5 5) 283 | 284 | After clipping: 0 ID=0 comp=1 @ (10 0 0) neighbors=[4 1 5 ] clips[] 285 | 1 ID=1 comp=1 @ (0.6 0 0) neighbors=[3 2 0 ] clips[10 ] 286 | 2 ID=2 comp=1 @ (0 0.6 0) neighbors=[1 3 6 ] clips[10 ] 287 | 3 ID=3 comp=1 @ (0 0 0.6) neighbors=[2 1 7 ] clips[10 ] 288 | 4 ID=4 comp=2 @ (10 0 6.5) neighbors=[5 7 0 ] clips[20 ] 289 | 5 ID=5 comp=2 @ (10 6.5 0) neighbors=[6 4 0 ] clips[20 ] 290 | 6 ID=6 comp=2 @ (0 1.5 0) neighbors=[7 5 2 ] clips[20 ] 291 | 7 ID=7 comp=2 @ (0 0 1.5) neighbors=[4 6 3 ] clips[20 ] 292 | 293 | Moments: 90.3807 (6.84598 1.64115 1.64115) 294 | --------------------------------------------------------------------------------