├── .clang-format ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── tests.yml │ └── wheels.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── MANIFEST.in ├── README.md ├── assets ├── 2D_polygon_text.png ├── api-doc.svg ├── pl_3d.png ├── pl_logo.png ├── pl_logo_poly.png └── polylidar_3D_architecture.jpg ├── bench ├── CMakeLists.txt ├── bench_mesh.cpp ├── bench_mesh_paper.cpp ├── records │ ├── 04_03_2019.txt │ ├── 04_03_2019_2.txt │ ├── 04_04_2019.txt │ ├── 04_06_2019.txt │ ├── 06_07_2020.txt │ └── bench_mesh_paper.json └── run-bench.cpp ├── cmake ├── Extras.cmake ├── FindSphinx.cmake └── GperftoolsConfig.cmake ├── dev-requirements.txt ├── docs ├── .nojekyll ├── _images │ ├── opc.png │ ├── opc_mesh.png │ ├── opc_polygons.png │ ├── pl_logo.png │ ├── tutorial_Python_basicdemo_14_0.png │ ├── tutorial_Python_basicdemo_17_0.png │ ├── tutorial_Python_basicdemo_21_1.png │ ├── tutorial_Python_basicdemo_6_1.png │ ├── tutorial_Python_organizeddemo_10_0.png │ ├── tutorial_Python_organizeddemo_12_0.png │ ├── tutorial_Python_organizeddemo_22_0.png │ ├── tutorial_Python_organizeddemo_27_0.png │ ├── tutorial_Python_organizeddemo_37_0.png │ ├── tutorial_Python_organizeddemo_5_0.jpg │ ├── tutorial_Python_organizeddemo_6_1.png │ └── tutorial_Python_organizeddemo_7_0.png ├── _static │ ├── basic.css │ ├── collapsible-lists │ │ ├── LICENSE.md │ │ ├── css │ │ │ ├── button-closed.png │ │ │ ├── button-open.png │ │ │ ├── button.png │ │ │ ├── list-item-contents.png │ │ │ ├── list-item-last-open.png │ │ │ ├── list-item-last.png │ │ │ ├── list-item-open.png │ │ │ ├── list-item-root.png │ │ │ ├── list-item.png │ │ │ └── tree_view.css │ │ └── js │ │ │ ├── CollapsibleLists.compressed.js │ │ │ └── apply-collapsible-lists.js │ ├── cpp_tutorial │ │ ├── opc.png │ │ ├── opc_mesh.png │ │ └── opc_polygons.png │ ├── css │ │ ├── badge_only.css │ │ └── theme.css │ ├── doctools.js │ ├── documentation_options.js │ ├── file.png │ ├── fonts │ │ ├── Inconsolata-Bold.ttf │ │ ├── Inconsolata-Regular.ttf │ │ ├── Inconsolata.ttf │ │ ├── Lato-Bold.ttf │ │ ├── Lato-Regular.ttf │ │ ├── Lato │ │ │ ├── lato-bold.eot │ │ │ ├── lato-bold.ttf │ │ │ ├── lato-bold.woff │ │ │ ├── lato-bold.woff2 │ │ │ ├── lato-bolditalic.eot │ │ │ ├── lato-bolditalic.ttf │ │ │ ├── lato-bolditalic.woff │ │ │ ├── lato-bolditalic.woff2 │ │ │ ├── lato-italic.eot │ │ │ ├── lato-italic.ttf │ │ │ ├── lato-italic.woff │ │ │ ├── lato-italic.woff2 │ │ │ ├── lato-regular.eot │ │ │ ├── lato-regular.ttf │ │ │ ├── lato-regular.woff │ │ │ └── lato-regular.woff2 │ │ ├── RobotoSlab-Bold.ttf │ │ ├── RobotoSlab-Regular.ttf │ │ ├── RobotoSlab │ │ │ ├── roboto-slab-v7-bold.eot │ │ │ ├── roboto-slab-v7-bold.ttf │ │ │ ├── roboto-slab-v7-bold.woff │ │ │ ├── roboto-slab-v7-bold.woff2 │ │ │ ├── roboto-slab-v7-regular.eot │ │ │ ├── roboto-slab-v7-regular.ttf │ │ │ ├── roboto-slab-v7-regular.woff │ │ │ └── roboto-slab-v7-regular.woff2 │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── jquery-3.5.1.js │ ├── jquery.js │ ├── js │ │ ├── modernizr.min.js │ │ └── theme.js │ ├── language_data.js │ ├── minus.png │ ├── organized │ │ ├── l515_mesh_smooth.png │ │ ├── l515_opc.png │ │ ├── l515_polygons.png │ │ ├── organized_mesh.png │ │ ├── organized_pc_raw.png │ │ └── organized_polygons.png │ ├── pl_logo.png │ ├── plus.png │ ├── pygments.css │ ├── searchtools.js │ ├── theme_overrides.css │ ├── underscore-1.13.1.js │ └── underscore.js ├── builddocs.html ├── cpp_api │ ├── class_polylidar_1_1_delaunator_1_1_delaunator.html │ ├── class_polylidar_1_1_matrix.html │ ├── class_polylidar_1_1_mesh_helper_1_1_half_edge_triangulation.html │ ├── class_polylidar_1_1_polylidar3_d.html │ ├── class_polylidar_1_1_utility_1_1_timer.html │ ├── cpp_library_root.html │ ├── define__delaunator_8hpp_1aab75489dd91b143a909025f8368e4009.html │ ├── define__types_8hpp_1a1d095d37fed141ea28ec04974442c0ef.html │ ├── define__types_8hpp_1a23804f18a0fbd9b4e157ae31b457bd10.html │ ├── define__types_8hpp_1a2d9cb1057a4e14234548ae19b0f3443d.html │ ├── define__types_8hpp_1a3f22be63c8fea45a7fa5cad21f12695d.html │ ├── define__types_8hpp_1a42420c51823910aab1a5fdc9dfe3c5d5.html │ ├── define__types_8hpp_1a525335710b53cb064ca56b936120431e.html │ ├── define__types_8hpp_1a70a969a6491e2de5132ae9aba81a4ec9.html │ ├── define__types_8hpp_1a7dea8871416165edb4f7173bcb2eda11.html │ ├── define__types_8hpp_1a8cee264b5d4e3db3070756c9746c2151.html │ ├── define__types_8hpp_1ab2eac6ae5b6808421541f54b4f036372.html │ ├── define__types_8hpp_1ab43aa29c4419785679216d072ec4cdfb.html │ ├── define__types_8hpp_1ad8ab559c0f1938709b6e578f862a8ce7.html │ ├── define__types_8hpp_1ae71449b1cc6e6250b91f539153a7a0d3.html │ ├── define__types_8hpp_1af2ca5f5b76596d9bbd6a67a4764ae2be.html │ ├── dir_include.html │ ├── dir_include_Polylidar.html │ ├── dir_include_Polylidar_Delaunator.html │ ├── dir_include_Polylidar_Mesh.html │ ├── file_include_Polylidar_Core.hpp.html │ ├── file_include_Polylidar_Delaunator_Delaunator.hpp.html │ ├── file_include_Polylidar_Mesh_MeshHelper.hpp.html │ ├── file_include_Polylidar_Polylidar.hpp.html │ ├── file_include_Polylidar_Types.hpp.html │ ├── file_include_Polylidar_Utility.hpp.html │ ├── file_include_Polylidar_UtilityMath.hpp.html │ ├── function__core_8hpp_1a1f316f22f93e14c1a249b69beebba10b.html │ ├── function__core_8hpp_1a2009c70a2125ad04dd6e40e10bcbdb6a.html │ ├── function__core_8hpp_1a3263a29f4127df8843e459510f6d97e5.html │ ├── function__core_8hpp_1a4b9651a843a1229e8ca0dbd3156febcc.html │ ├── function__core_8hpp_1a70489c955319359ace18fef3176f2de1.html │ ├── function__core_8hpp_1a85802683bd9f7f8dba26d1729ad120cf.html │ ├── function__core_8hpp_1ab98cac6c050263d49ddef3bcf0d14036.html │ ├── function__core_8hpp_1ac88b3986070c1372fc522341a6514f15.html │ ├── function__core_8hpp_1add4a1efaafa2041846ba2c94d20c26b7.html │ ├── function__core_8hpp_1adde24b475c79c5e0be903f2b028b33d9.html │ ├── function__core_8hpp_1af677514aacc51035b4e2d05bba23ca05.html │ ├── function__mesh_helper_8hpp_1a0795f32234b0753964afbba2e25b11f1.html │ ├── function__mesh_helper_8hpp_1a0a5f2849ec799733e0f81afd6bed52b8.html │ ├── function__mesh_helper_8hpp_1a0fcc773725c30395ec9e9ffa9039b828.html │ ├── function__mesh_helper_8hpp_1a2d4bb214cc0b745369fa53d794809aa5.html │ ├── function__mesh_helper_8hpp_1a394d710b518258deec8d884b9737d5bc.html │ ├── function__mesh_helper_8hpp_1a3ee540b953482106943613bfc88bb702.html │ ├── function__mesh_helper_8hpp_1a3f6f46f413ce11ac7ff641e5be6228a8.html │ ├── function__mesh_helper_8hpp_1a4b714130e02f1532a43d0fbf5f2e1564.html │ ├── function__mesh_helper_8hpp_1a6dcf4d32a3ee2497c6a6531242b1ab96.html │ ├── function__mesh_helper_8hpp_1a8253ecf63b14865cd30a37d80f7ebb7b.html │ ├── function__mesh_helper_8hpp_1ac0df864c0d84953a2168aae444463f45.html │ ├── function__mesh_helper_8hpp_1ad5eec264e9112ea3274fd0e482c0b860.html │ ├── function__mesh_helper_8hpp_1af2f5a484e368296155be589dc1582d17.html │ ├── function__utility_8hpp_1a0564ff05b3cece45c33d3da2be2a3d18.html │ ├── function__utility_8hpp_1a1f9f63580a80812a401deff72b328fd2.html │ ├── function__utility_8hpp_1a32ed1d575166842388914c19dbf4ee37.html │ ├── function__utility_8hpp_1a3492984e36d695d627dbee908655bda9.html │ ├── function__utility_8hpp_1a6279e829eac7208343304e98af7edd2c.html │ ├── function__utility_8hpp_1a80a81b1bc1d0622a664461278f812c6b.html │ ├── function__utility_8hpp_1a897603d7c94440bdc16cd802109e7322.html │ ├── function__utility_8hpp_1abd490d4eba9cfea63b99fa22b66e095b.html │ ├── function__utility_8hpp_1accdcc0ee5e84acf219e509dd656bae4a.html │ ├── function__utility_8hpp_1ad3899922c5306e5c2fe940b0b5116302.html │ ├── function__utility_8hpp_1adf0acd4d7f0129216d005c5048828863.html │ ├── function__utility_8hpp_1af2b8f0dfdf23b0c7867c60290dd20209.html │ ├── function__utility_8hpp_1af486b42b4386373131e33d10effa2089.html │ ├── function__utility_math_8hpp_1a26a15551cd5a1a9ab3505d972ff59357.html │ ├── function__utility_math_8hpp_1a2eb226872f1b37bf51e22ce02cab642f.html │ ├── function__utility_math_8hpp_1a466c1d74ae5b142b06bbce541aa90053.html │ ├── function__utility_math_8hpp_1a468a3acbfb944236a42464f59fb3fd2c.html │ ├── function__utility_math_8hpp_1a5d203b5b0dbaad40858e729adf651b49.html │ ├── function__utility_math_8hpp_1a63c41a05f6be15da2be8b5c1aeb76e3b.html │ ├── function__utility_math_8hpp_1a72028d52fab531c7e7d3402e2459214b.html │ ├── function__utility_math_8hpp_1a8244427fefea52816f58db3d959c1f51.html │ ├── function__utility_math_8hpp_1aae7fdef8c6c54ef9852b116ac45be081.html │ ├── function__utility_math_8hpp_1ad4cf028794ad41b5b96aa3410f5ee676.html │ ├── function__utility_math_8hpp_1ad83063c4e395cce3d244fe54abdaee46.html │ ├── function__utility_math_8hpp_1ae03792bc59ac31647991255e5943c95d.html │ ├── function__utility_math_8hpp_1af05c745bac954b5612ce0770794c7ee7.html │ ├── function__utility_math_8hpp_1af16ec1926a2335a668880e0951205474.html │ ├── namespace_Polylidar.html │ ├── namespace_Polylidar__Core.html │ ├── namespace_Polylidar__Delaunator.html │ ├── namespace_Polylidar__MeshHelper.html │ ├── namespace_Polylidar__Utility.html │ ├── namespace_Polylidar__Utility__Math.html │ ├── program_listing_file_include_Polylidar_Core.hpp.html │ ├── program_listing_file_include_Polylidar_Delaunator_Delaunator.hpp.html │ ├── program_listing_file_include_Polylidar_Mesh_MeshHelper.hpp.html │ ├── program_listing_file_include_Polylidar_Polylidar.hpp.html │ ├── program_listing_file_include_Polylidar_Types.hpp.html │ ├── program_listing_file_include_Polylidar_Utility.hpp.html │ ├── program_listing_file_include_Polylidar_UtilityMath.hpp.html │ ├── struct_polylidar_1_1_extreme_point.html │ ├── struct_polylidar_1_1_plane_data.html │ ├── struct_polylidar_1_1_polygon.html │ ├── typedef__mesh_helper_8hpp_1a5a3fec4bd6f8157d80d99b69780b7c0c.html │ ├── typedef__mesh_helper_8hpp_1a6cc699607d9890f4ac89d51249ce23cf.html │ ├── typedef__types_8hpp_1a047cfd2555d7032f95197f210777b4fa.html │ ├── typedef__types_8hpp_1a151f5359a0516169fbafa0e6c9951175.html │ ├── typedef__types_8hpp_1a1e2a29b8f7fee3fa66e78ab6aa1f7767.html │ ├── typedef__types_8hpp_1a6783a447a165ea396cf8858152752049.html │ ├── typedef__types_8hpp_1a694d2b6ca322939ac8b1716b80bb919a.html │ ├── typedef__types_8hpp_1a6fd40a1a3c08ebb65a2f11e3b21bfbfa.html │ ├── typedef__types_8hpp_1a979e2b7eb8f15adb3f986eccc3fdc0a7.html │ ├── typedef__types_8hpp_1a9d00629f79c841e79a258bb36637cb32.html │ ├── typedef__types_8hpp_1ac93001b58b8f7837585ecb11570954e2.html │ ├── unabridged_orphan.html │ ├── variable__types_8hpp_1a0e9042f4830755cf4427a031dff73e32.html │ ├── variable__types_8hpp_1a1f5d04597ffd4b5cca6fb1ee2e9b780c.html │ ├── variable__types_8hpp_1a4079d21632ff8d22636e133700c44fed.html │ ├── variable__types_8hpp_1a60b9643ef6a7874391123c78d41e03ef.html │ ├── variable__types_8hpp_1a6b4cab8513b8c1a6705de5e08744b07c.html │ ├── variable__types_8hpp_1ade7920342e2ab6fee9a3e9b21d70f567.html │ ├── variable__types_8hpp_1affa84bf61aeceb782f8b1bd3bac39fc9.html │ └── variable__utility_8hpp_1afc5adb5092e2cf0c283ed7c37b1772dd.html ├── genindex.html ├── index.html ├── install_instructions.html ├── introduction.html ├── objects.inv ├── python_api │ ├── polylidar.Delaunator.html │ ├── polylidar.HalfEdgeTriangulation.html │ ├── polylidar.MatrixDouble.html │ ├── polylidar.MatrixFloat.html │ ├── polylidar.MatrixInt.html │ ├── polylidar.MatrixUInt8.html │ ├── polylidar.MatrixULongInt.html │ ├── polylidar.Polygon.html │ ├── polylidar.Polylidar3D.html │ ├── polylidar.VectorDouble.html │ ├── polylidar.VectorInt.html │ ├── polylidar.VectorUInt8.html │ ├── polylidar.VectorULongInt.html │ ├── polylidar.bilateral_filter_normals.html │ ├── polylidar.create_tri_mesh_copy.html │ ├── polylidar.extract_point_cloud_from_float_depth.html │ ├── polylidar.extract_tri_mesh_from_float_depth.html │ ├── polylidar.extract_tri_mesh_from_organized_point_cloud.html │ ├── polylidar.get_polylidar_version.html │ ├── polylidar.html │ ├── polylidar.laplacian_filter_vertices.html │ ├── polylidar.polylidarutil.plane_filtering.JOIN_STYLE.html │ ├── polylidar.polylidarutil.plane_filtering.Polygon.html │ ├── polylidar.polylidarutil.plane_filtering.R.html │ ├── polylidar.polylidarutil.plane_filtering.add_column.html │ ├── polylidar.polylidarutil.plane_filtering.create_kd_tree.html │ ├── polylidar.polylidarutil.plane_filtering.filter_planes.html │ ├── polylidar.polylidarutil.plane_filtering.filter_planes_and_holes.html │ ├── polylidar.polylidarutil.plane_filtering.get_points.html │ ├── polylidar.polylidarutil.plane_filtering.html │ ├── polylidar.polylidarutil.plane_filtering.plot_indices_text.html │ ├── polylidar.polylidarutil.plane_filtering.plot_poly.html │ ├── polylidar.polylidarutil.plane_filtering.recover_3d.html │ └── polylidar.robust_predicates_activated.html ├── search.html ├── searchindex.js └── tutorial │ ├── C++ │ ├── basic.html │ └── index.html │ └── Python │ ├── basicdemo.html │ ├── basicdemo.ipynb │ ├── index.html │ ├── organizeddemo.html │ └── organizeddemo.ipynb ├── examples ├── CMakeLists.txt ├── cpp │ ├── CMakeLists.txt │ ├── README.md │ ├── debug.cpp │ ├── polylidar-full-example.cpp │ ├── simple.cpp │ └── vis_util.hpp └── python │ ├── basic25d.py │ ├── basic25d_algorithm.py │ ├── basic2d.py │ ├── basic2d_algorithm.py │ ├── basic_mesh.py │ ├── bilateral.py │ ├── extras │ ├── open3d_mesh.py │ ├── test_delaunay.py │ ├── test_matrix.py │ └── test_o3d.py │ ├── for_landing │ ├── PolylidarParams.yaml │ ├── PolylidarParamsSimple.yaml │ ├── basic_rooftop_classification.py │ ├── basic_rooftop_classification_algorithm.py │ ├── o3d_slow_down.py │ └── test_classified.py │ ├── for_paper │ ├── basic25d_algorithm_explained.py │ ├── bench_mesh_report.py │ ├── california_points.py │ ├── generate_logo.py │ ├── manifold_meshes.py │ ├── mesh_examples.py │ ├── plane_segmentation.py │ ├── polygon_example.py │ ├── polygon_example_research_statement.py │ └── polygon_extraction_from_mesh.py │ ├── notebooks │ ├── BasicDemo.ipynb │ └── OrganizedDemo.ipynb │ ├── realsense_mesh.py │ ├── robust.py │ └── util │ ├── helper_mesh.py │ ├── helper_polylidar.py │ ├── mesh_util.py │ └── realsense_util.py ├── fixtures └── .gitignore ├── include └── Polylidar │ ├── Core.hpp │ ├── Delaunator │ └── Delaunator.hpp │ ├── Mesh │ └── MeshHelper.hpp │ ├── Polylidar.hpp │ ├── Types.hpp │ ├── Utility.hpp │ └── UtilityMath.hpp ├── pyproject.toml ├── pytest.ini ├── requirements.txt ├── scripts ├── make_release.sh └── manage_versions.py ├── setup.py ├── src ├── CMakeLists.txt ├── Polylidar │ ├── CMakeLists.txt │ ├── Core.cpp │ ├── Delaunator.cpp │ ├── MeshFilter.cpp │ ├── MeshHelper.cpp │ ├── PLConfig.cpp │ ├── PLConfig.hpp.in │ └── Polylidar.cpp ├── Python │ ├── CMakeLists.txt │ ├── LICENSE │ ├── MANIFEST.in │ ├── README.md │ ├── make_install_pip_package.cmake │ ├── make_python_package.cmake │ ├── polylidar │ │ ├── __init__.py │ │ ├── __main__.py │ │ └── polylidarutil │ │ │ ├── __init__.py │ │ │ ├── descartes_patch.py │ │ │ ├── line_mesh.py │ │ │ ├── open3d_util.py │ │ │ └── plane_filtering.py │ ├── polylidar_pybind │ │ ├── docstring │ │ │ ├── docstring.cpp │ │ │ └── docstring.hpp │ │ ├── polylidar_pybind.cpp │ │ └── polylidar_pybind.hpp │ ├── requirements.txt │ └── setup.py └── version.txt ├── src_docs ├── .nojekyll ├── Doxyfile ├── _static │ ├── cpp_tutorial │ │ ├── opc.png │ │ ├── opc_mesh.png │ │ └── opc_polygons.png │ ├── organized │ │ ├── l515_mesh_smooth.png │ │ ├── l515_opc.png │ │ ├── l515_polygons.png │ │ ├── organized_mesh.png │ │ ├── organized_pc_raw.png │ │ └── organized_polygons.png │ ├── pl_logo.png │ └── theme_overrides.css ├── builddocs.rst ├── conf.py ├── index.rst ├── install_instructions.rst ├── introduction.rst ├── make.bat ├── make_docs.py └── tutorial │ ├── C++ │ ├── basic.rst │ └── index.rst │ └── Python │ ├── basicdemo.nblink │ ├── index.rst │ └── organizeddemo.nblink ├── tests ├── CMakeLists.txt ├── __init__.py ├── cpp │ ├── CMakeLists.txt │ └── run-tests.cpp └── python │ ├── __init__.py │ ├── benchmark_test.py │ ├── conftest.py │ ├── generated_test.py │ ├── helpers │ ├── __init__.py │ └── utils.py │ └── shape_test.py └── thirdparty ├── CMakeLists.txt ├── FastExp ├── LICENSE ├── fastexp.h ├── ieee.h └── product.h ├── npy └── npy │ └── npy.h └── predicates ├── CMakeLists.txt ├── README.md ├── constants.c ├── constants.h ├── predicates.c ├── predicates.h ├── predicates.hpp ├── predicatesDLLExport.h ├── printing.c └── random.c /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # Mapbox.Variant C/C+ style 3 | Language: Cpp 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: true 16 | AllowShortLoopsOnASingleLine: true 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: false 29 | AfterObjCDeclaration: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Custom 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | ColumnLimit: 120 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 49 | IncludeCategories: 50 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 51 | Priority: 2 52 | - Regex: '^(<|"(gtest|isl|json)/)' 53 | Priority: 3 54 | - Regex: '.*' 55 | Priority: 1 56 | IndentCaseLabels: false 57 | IndentWidth: 4 58 | IndentWrappedFunctionNames: false 59 | KeepEmptyLinesAtTheStartOfBlocks: true 60 | MacroBlockBegin: '' 61 | MacroBlockEnd: '' 62 | MaxEmptyLinesToKeep: 1 63 | NamespaceIndentation: None 64 | ObjCBlockIndentWidth: 2 65 | ObjCSpaceAfterProperty: false 66 | ObjCSpaceBeforeProtocolList: true 67 | PenaltyBreakBeforeFirstCallParameter: 19 68 | PenaltyBreakComment: 300 69 | PenaltyBreakFirstLessLess: 120 70 | PenaltyBreakString: 1000 71 | PenaltyExcessCharacter: 1000000 72 | PenaltyReturnTypeOnItsOwnLine: 60 73 | PointerAlignment: Left 74 | ReflowComments: true 75 | SortIncludes: false 76 | SpaceAfterCStyleCast: false 77 | SpaceBeforeAssignmentOperators: true 78 | SpaceBeforeParens: ControlStatements 79 | SpaceInEmptyParentheses: false 80 | SpacesBeforeTrailingComments: 1 81 | SpacesInAngles: false 82 | SpacesInContainerLiterals: true 83 | SpacesInCStyleCastParentheses: false 84 | SpacesInParentheses: false 85 | SpacesInSquareBrackets: false 86 | Standard: Cpp11 87 | TabWidth: 4 88 | UseTab: Never 89 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. Using Polylidar as Python or pure C++. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Environment (please complete the following information):** 20 | - OS: [e.g. WIndows, Linux, MacOS] 21 | - Architecture (x64, Arm) 22 | - Version [e.g. 0.0.6] 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - dev 8 | env: 9 | NPROC: 2 10 | 11 | jobs: 12 | 13 | # Create Source Distribution and upload artifacts 14 | python_test: 15 | runs-on: ${{ matrix.os }} 16 | name: Python Tests 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: ['ubuntu-latest', 'windows-latest', 'macos-latest'] 21 | python-version: ['3.7', '3.8', '3.9', '3.10' ] 22 | exclude: 23 | # excludes 3.10 on macOS 24 | - os: macos-latest 25 | python-version: '3.10' 26 | steps: 27 | - name: Checkout code 28 | uses: actions/checkout@v2 29 | 30 | - name: Setup Python 31 | uses: actions/setup-python@v2 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | architecture: x64 35 | 36 | # - name: Install OpenMP for MacOS 37 | # if: ${{ matrix.os == 'macos-latest' }} 38 | # run: brew install libomp 39 | 40 | - name: Setup CMake 41 | uses: jwlawson/actions-setup-cmake@v1.9 42 | with: 43 | cmake-version: '3.19.x' 44 | 45 | - name: Install requirements 46 | run: python -m pip install --upgrade pip setuptools wheel pytest 47 | 48 | - name: Build sdist 49 | run: | 50 | python setup.py sdist -d wheelhouse 51 | - name: Show files 52 | shell: bash 53 | run: ls -lh wheelhouse 54 | 55 | - name: Install from sdist 56 | shell: bash 57 | run: | 58 | pip install wheelhouse/*.tar.gz 59 | 60 | - name: Run Tests 61 | run: | 62 | pytest -------------------------------------------------------------------------------- /.github/workflows/wheels.yml: -------------------------------------------------------------------------------- 1 | name: Wheels 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | tags: 8 | - v* 9 | env: 10 | NPROC: 2 11 | 12 | jobs: 13 | 14 | # Create Source Distribution and upload artifacts 15 | build_sdist: 16 | name: Source Distribution 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v2 21 | 22 | - name: Setup Python 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: '3.8' 26 | 27 | - name: Setup CMake 28 | uses: jwlawson/actions-setup-cmake@v1.9 29 | with: 30 | cmake-version: '3.19.x' 31 | 32 | - name: Install requirements 33 | run: pip install --user twine 34 | 35 | - name: Build sdist 36 | run: | 37 | python setup.py sdist -d wheelhouse 38 | 39 | - name: Install from sdist 40 | run: | 41 | pip install --user wheelhouse/*.tar.gz 42 | 43 | - name: Check sdist 44 | run: | 45 | python -m twine check wheelhouse/* 46 | 47 | - name: Upload sdist 48 | uses: actions/upload-artifact@v2 49 | with: 50 | name: wheels 51 | path: wheelhouse/*.tar.gz 52 | 53 | # Create Binary Distribution (wheels) and upload artifacts 54 | build_wheels: 55 | runs-on: ${{ matrix.os }} 56 | name: Binary Wheels 57 | strategy: 58 | fail-fast: false 59 | matrix: 60 | include: 61 | - os: ubuntu-latest 62 | cibw-arch: manylinux_x86_64 63 | - os: windows-latest 64 | cibw-arch: win_amd64 65 | - os: macos-latest 66 | cibw-arch: macosx_x86_64 67 | - os: macos-latest 68 | cibw-arch: macosx_arm64 69 | - os: macos-latest 70 | cibw-arch: macosx_universal2 71 | # - os: ubuntu-latest 72 | # cibw-arch: manylinux_i686 73 | # - os: windows-latest 74 | # cibw-arch: win32 75 | # python-arch: x86 76 | # cmake-arch: -A Win32 77 | env: 78 | CIBW_SKIP: "cp27-* cp35-* cp36-* cp311-* pp*" 79 | steps: 80 | - name: Checkout source code 81 | uses: actions/checkout@v2 82 | 83 | - name: Set up Python version 84 | uses: actions/setup-python@v2 85 | with: 86 | python-version: 3.8 87 | 88 | # - name: Install OpenMP for MacOS 89 | # if: ${{ matrix.os == 'macos-latest' }} 90 | # run: brew install libomp 91 | 92 | - name: Setup CMake 93 | uses: jwlawson/actions-setup-cmake@v1.9 94 | with: 95 | cmake-version: '3.19.x' 96 | 97 | - name: Install requirements 98 | run: | 99 | python -m pip install cibuildwheel twine 100 | 101 | - name: Configure cibuildwheel 102 | shell: bash 103 | run: | 104 | CMAKE_OSX_ARCHITECTURES=${{ matrix.cibw-arch == 'macosx_x86_64' && 'x86_64' || matrix.cibw-arch == 'macosx_arm64' && 'arm64' || matrix.cibw-arch == 'macosx_universal2' && '"arm64;x86_64"' || '' }} 105 | echo "CIBW_ARCHS_MACOS=x86_64 arm64 universal2" >> $GITHUB_ENV 106 | echo "CIBW_BUILD=*-${{ matrix.cibw-arch }}" >> $GITHUB_ENV 107 | 108 | - name: Build Wheels 109 | run: | 110 | python -m cibuildwheel --output-dir wheelhouse 111 | 112 | - name: Show files 113 | shell: bash 114 | run: ls -lh wheelhouse 115 | 116 | - name: Upload wheels to artifact 117 | uses: actions/upload-artifact@v2 118 | with: 119 | path: wheelhouse/*.whl 120 | name: wheels 121 | 122 | upload_wheels: 123 | name: Upload Wheels to PyPI 124 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 125 | runs-on: ubuntu-latest 126 | needs: [build_sdist, build_wheels] 127 | 128 | steps: 129 | - name: Collect sdist and wheels 130 | uses: actions/download-artifact@v2 131 | with: 132 | name: wheels 133 | path: wheelhouse 134 | 135 | # # Upload to Testing PyPI 136 | # - name: Publish package to TestPyPI 137 | # uses: pypa/gh-action-pypi-publish@master 138 | # with: 139 | # user: __token__ 140 | # password: ${{ secrets.test_pypi_password }} 141 | # packages_dir: wheelhouse/ 142 | # repository_url: https://test.pypi.org/legacy/ 143 | # skip_existing: true 144 | 145 | # Publish to official PyPI channel 146 | - name: Publish package to PyPI 147 | uses: pypa/gh-action-pypi-publish@master 148 | with: 149 | user: __token__ 150 | password: ${{ secrets.pypi_password }} 151 | packages_dir: wheelhouse/ 152 | skip_existing: true 153 | 154 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | fixtures/meshes 2 | fixtures/temp 3 | fixtures/realsense 4 | fixtures/pointsets 5 | scratch 6 | cmake-build 7 | assets/scratch 8 | tests/fixtures/meshes 9 | o3d_slow_down 10 | # Documentation Sphinx 11 | src_docs/_build 12 | src_docs/_out 13 | src_docs/cpp_api 14 | src_docs/python_api 15 | docs/.doctrees 16 | docs/.buildinfo 17 | docs/_sources 18 | docds/objects.inv 19 | # docs 20 | 21 | # examples/cpp/bin 22 | .vscode 23 | .nox 24 | archive 25 | scratch 26 | solns 27 | *.prof 28 | *.o 29 | *.swp 30 | *.swo 31 | *.so 32 | *.pyc 33 | *.dll 34 | *.exp 35 | *.lib 36 | *.obj 37 | .vscode/ipch 38 | 39 | # Byte-compiled / optimized / DLL files 40 | __pycache__/ 41 | *.py[cod] 42 | *$py.class 43 | 44 | # C extensions 45 | *.so 46 | 47 | # Distribution / packaging 48 | .Python 49 | build/ 50 | develop-eggs/ 51 | dist/ 52 | downloads/ 53 | eggs/ 54 | .eggs/ 55 | lib/ 56 | lib64/ 57 | parts/ 58 | sdist/ 59 | var/ 60 | wheels/ 61 | *.egg-info/ 62 | .installed.cfg 63 | *.egg 64 | MANIFEST 65 | 66 | # PyInstaller 67 | # Usually these files are written by a python script from a template 68 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 69 | *.manifest 70 | *.spec 71 | 72 | # Installer logs 73 | pip-log.txt 74 | pip-delete-this-directory.txt 75 | 76 | # Unit test / coverage reports 77 | htmlcov/ 78 | .tox/ 79 | .coverage 80 | .coverage.* 81 | .cache 82 | nosetests.xml 83 | coverage.xml 84 | *.cover 85 | .hypothesis/ 86 | .pytest_cache/ 87 | 88 | # Translations 89 | *.mo 90 | *.pot 91 | 92 | # Django stuff: 93 | .static_storage/ 94 | .media/ 95 | local_settings.py 96 | 97 | # Flask stuff: 98 | instance/ 99 | .webassets-cache 100 | 101 | # Scrapy stuff: 102 | .scrapy 103 | 104 | # Sphinx documentation 105 | docs/_build/ 106 | 107 | # PyBuilder 108 | target/ 109 | 110 | # Jupyter Notebook 111 | .ipynb_checkpoints 112 | 113 | # pyenv 114 | .python-version 115 | 116 | # celery beat schedule file 117 | celerybeat-schedule 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | 144 | !build/.gitignore 145 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | # Project Details 3 | set(PROJECT_NAME "Polylidar") 4 | set(PROJECT_VERSION 1.0.0) 5 | set(PROJECT_EMAIL "") 6 | set(PROJECT_HOME "") 7 | set(PROJECT_DOCS "") 8 | set(PROJECT_CODE "") 9 | set(PROJECT_ISSUES "") 10 | set(PYPI_PACKAGE_NAME "polylidar") 11 | # Set Project Properties 12 | project(${PROJECT_NAME} VERSION ${PROJECT_VERSION} 13 | DESCRIPTION "Fast Polygon Extraction from Point Clouds and Meshes" 14 | LANGUAGES CXX) 15 | # Set Global Properties 16 | set(CMAKE_CXX_STANDARD 14) 17 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 18 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 19 | set(CMAKE_CXX_EXTENSIONS OFF) 20 | 21 | if(NOT CMAKE_BUILD_TYPE) 22 | set(CMAKE_BUILD_TYPE Release) 23 | endif() 24 | 25 | # Include cmake folder 26 | list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") 27 | include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Extras.cmake) 28 | 29 | set(ORIG_CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) 30 | 31 | # Output Folders 32 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) 33 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) 34 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) 35 | 36 | # Options for PL 37 | option(PL_BUILD_PYMODULE "PL - Build Python Module" ON) 38 | option(PL_BUILD_TESTS "PL - Build Tests" ON) 39 | option(PL_BUILD_BENCHMARKS "PL - Build Benchmarks" OFF) 40 | option(PL_BUILD_EXAMPLES "PL - Build Examples" ON) 41 | option(PL_WITH_OPENMP "PL - Build with OpenMP Support" ON) 42 | option(PL_BUILD_FASTGA "PL - Build FastGA with Example" OFF) 43 | option(PL_USE_ROBUST_PREDICATES "PL - Build with Fast Geometric Predicates" OFF) 44 | option(PL_BUILD_WERROR "PL - Add Werror flag to build (turns warnings into errors)" OFF) 45 | 46 | # Add any dependencies needed by our library 47 | add_subdirectory("thirdparty") 48 | 49 | # Build our library 50 | add_subdirectory("src") 51 | 52 | # Build examples if configured 53 | if(PL_BUILD_EXAMPLES) 54 | add_subdirectory("examples") 55 | endif() 56 | 57 | # Build tests if configured 58 | if(PL_BUILD_TESTS) 59 | add_subdirectory("tests") 60 | endif() 61 | 62 | # Build benchmarks if configured 63 | if(PL_BUILD_BENCHMARKS) 64 | add_subdirectory("bench") 65 | endif() 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeremy Castagno 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include LICENSE 3 | recursive-include bench *.cpp 4 | recursive-include bench *.txt 5 | recursive-include cmake *.cmake 6 | recursive-include examples *.cpp 7 | recursive-include examples *.py 8 | recursive-include examples *.txt 9 | recursive-include include *.hpp 10 | recursive-include scripts *.py 11 | recursive-include scripts *.sh 12 | recursive-include src *.cmake 13 | recursive-include src *.cpp 14 | recursive-include src *.hpp 15 | recursive-include src *.in 16 | recursive-include src *.md 17 | recursive-include src *.py 18 | recursive-include src *.txt 19 | recursive-include tests *.cpp 20 | recursive-include tests *.py 21 | recursive-include tests *.txt 22 | recursive-include thirdparty *.cmake 23 | recursive-include thirdparty *.cpp 24 | recursive-include thirdparty *.hpp 25 | recursive-include thirdparty *.h 26 | recursive-include thirdparty *LICENSE 27 | recursive-include thirdparty *.in 28 | recursive-include thirdparty *.md 29 | recursive-include thirdparty *.py 30 | recursive-include thirdparty *.txt -------------------------------------------------------------------------------- /assets/2D_polygon_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/assets/2D_polygon_text.png -------------------------------------------------------------------------------- /assets/api-doc.svg: -------------------------------------------------------------------------------- 1 | APIAPIdocsdocs -------------------------------------------------------------------------------- /assets/pl_3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/assets/pl_3d.png -------------------------------------------------------------------------------- /assets/pl_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/assets/pl_logo.png -------------------------------------------------------------------------------- /assets/pl_logo_poly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/assets/pl_logo_poly.png -------------------------------------------------------------------------------- /assets/polylidar_3D_architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/assets/polylidar_3D_architecture.jpg -------------------------------------------------------------------------------- /bench/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare( 4 | extern_googlebenchmark 5 | GIT_REPOSITORY https://github.com/google/benchmark.git 6 | GIT_TAG v1.5.0 7 | ) 8 | 9 | # This module depend on having googletest and googlebenchmark installed 10 | FetchContent_GetProperties(extern_googlebenchmark) 11 | if(NOT extern_googlebenchmark_POPULATED) 12 | FetchContent_Populate(extern_googlebenchmark) 13 | SET(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Enable testing of the benchmark library.") 14 | add_subdirectory(${extern_googlebenchmark_SOURCE_DIR} ${extern_googlebenchmark_BINARY_DIR}) 15 | endif() 16 | 17 | # Google Benchmark needs threads! 18 | find_package(Threads REQUIRED) 19 | find_package(Open3D REQUIRED) 20 | 21 | 22 | add_executable(run-bench ${CMAKE_CURRENT_SOURCE_DIR}/run-bench.cpp ${CMAKE_CURRENT_SOURCE_DIR}/bench_mesh.cpp ${CMAKE_CURRENT_SOURCE_DIR}/bench_mesh_paper.cpp 23 | ) 24 | # ${CMAKE_CURRENT_SOURCE_DIR}/parallelism_test.cpp 25 | target_link_libraries(run-bench PRIVATE Polylidar::PL benchmark Threads::Threads ${Open3D_LIBRARIES}) 26 | target_link_directories(run-bench PRIVATE ${Open3D_LIBRARY_DIRS}) 27 | target_include_directories(run-bench PRIVATE ${Open3D_INCLUDE_DIRS}) -------------------------------------------------------------------------------- /bench/bench_mesh_paper.cpp: -------------------------------------------------------------------------------- 1 | // These benchmarks explicitely time plane and polygon extraction 2 | // It also measures how CPU scores scale with the problem 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | const std::string SPARSE_MESH = "./fixtures/meshes/basement_chair_5cm.ply"; 17 | const std::string DENSE_MESH = "./fixtures/meshes/dense_first_floor_map_smoothed.ply"; 18 | 19 | using namespace Polylidar; 20 | namespace o3d = open3d; 21 | using TriMesh = MeshHelper::HalfEdgeTriangulation; 22 | 23 | static constexpr int max_threads = 8; 24 | 25 | inline void CustomArguments_Paper(benchmark::internal::Benchmark* b) 26 | { 27 | for (int i = 1; i <= 4; i += 1) 28 | for (int j = 1; j <= max_threads; j += 1) b->Args({i, j}); 29 | } 30 | 31 | class SparseMeshPaper : public benchmark::Fixture 32 | { 33 | public: 34 | std::shared_ptr sparse_mesh_o3d; 35 | TriMesh sparse_mesh; 36 | // Eigen::Matrix3d mesh_rotation; 37 | void SetUp(const ::benchmark::State& state) 38 | { 39 | 40 | // Sparse Mesh In O3D Format 41 | sparse_mesh_o3d = o3d::io::CreateMeshFromFile(SPARSE_MESH); 42 | // Sparse Mesh In HalfEdgeTriangulation 43 | auto triangles_ptr = reinterpret_cast(sparse_mesh_o3d->triangles_.data()); 44 | auto num_triangles = sparse_mesh_o3d->triangles_.size(); 45 | auto vertices_ptr = reinterpret_cast(sparse_mesh_o3d->vertices_.data()); 46 | auto num_vertices = sparse_mesh_o3d->vertices_.size(); 47 | Matrix triangles(triangles_ptr, num_triangles, 3); 48 | Matrix vertices(vertices_ptr, num_vertices, 3); 49 | 50 | sparse_mesh = MeshHelper::CreateTriMeshCopy(vertices, triangles, true); 51 | } 52 | }; 53 | 54 | class DenseMeshPaper : public benchmark::Fixture 55 | { 56 | public: 57 | std::shared_ptr dense_mesh_o3d; 58 | TriMesh dense_mesh; 59 | // Eigen::Matrix3d mesh_rotation; 60 | void SetUp(const ::benchmark::State& state) 61 | { 62 | 63 | // Sparse Mesh In O3D Format 64 | dense_mesh_o3d = o3d::io::CreateMeshFromFile(DENSE_MESH); 65 | auto triangles_ptr = reinterpret_cast(dense_mesh_o3d->triangles_.data()); 66 | auto num_triangles = dense_mesh_o3d->triangles_.size(); 67 | auto vertices_ptr = reinterpret_cast(dense_mesh_o3d->vertices_.data()); 68 | auto num_vertices = dense_mesh_o3d->vertices_.size(); 69 | Matrix triangles(triangles_ptr, num_triangles, 3); 70 | Matrix vertices(vertices_ptr, num_vertices, 3); 71 | 72 | dense_mesh = MeshHelper::CreateTriMeshCopy(vertices, triangles, true); 73 | } 74 | }; 75 | 76 | BENCHMARK_DEFINE_F(SparseMeshPaper, BM_Paper) 77 | (benchmark::State& st) 78 | { 79 | std::vector> normals = {{-0.0296, 0.0003, 0.9996}, 80 | {-0.9534, 0.3016, 0.0077}, 81 | {0.9365, -0.3371, 0.0963}, 82 | {0.3054, 0.9522, -0.012}, 83 | {0.0111 - 0.0113 - 0.9999}}; 84 | 85 | int num_normals = st.range(0); 86 | int num_threads = st.range(1); 87 | omp_set_num_threads(num_threads); 88 | Polylidar3D pl3d(0.0, 0.1, 1000, 6, 0.03, 0.95, 0.92, num_threads); 89 | const Matrix normals_mat((double*)(normals.data()), num_normals, 3); 90 | for (auto _ : st) 91 | { 92 | auto planes_and_polygons = pl3d.ExtractPlanesAndPolygonsOptimized(sparse_mesh, normals_mat); 93 | benchmark::DoNotOptimize(planes_and_polygons); 94 | } 95 | } 96 | 97 | BENCHMARK_DEFINE_F(DenseMeshPaper, BM_Paper) 98 | (benchmark::State& st) 99 | { 100 | std::vector> normals = { 101 | {0.0123, 0.0021, 0.9999}, {0.2241, -0.9744, 0.0171}, {-0.2207, 0.9752, -0.0144}, {0.9612, 0.2756, -0.0129}}; 102 | 103 | int num_normals = st.range(0); 104 | int num_threads = st.range(1); 105 | omp_set_num_threads(num_threads); 106 | Polylidar3D pl3d(0.0, 0.1, 1000, 6, 0.03, 0.95, 0.92, num_threads); 107 | const Matrix normals_mat((double*)(normals.data()), num_normals, 3); 108 | for (auto _ : st) 109 | { 110 | auto planes_and_polygons = pl3d.ExtractPlanesAndPolygonsOptimized(dense_mesh, normals_mat); 111 | benchmark::DoNotOptimize(planes_and_polygons); 112 | } 113 | } 114 | 115 | BENCHMARK_REGISTER_F(SparseMeshPaper, BM_Paper) 116 | ->Apply(CustomArguments_Paper) 117 | ->UseRealTime() 118 | ->Unit(benchmark::kMicrosecond); 119 | BENCHMARK_REGISTER_F(DenseMeshPaper, BM_Paper) 120 | ->Apply(CustomArguments_Paper) 121 | ->UseRealTime() 122 | ->Unit(benchmark::kMillisecond); 123 | -------------------------------------------------------------------------------- /bench/run-bench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Run the benchmark 4 | BENCHMARK_MAIN(); -------------------------------------------------------------------------------- /cmake/Extras.cmake: -------------------------------------------------------------------------------- 1 | function(target_link_libraries_system target) 2 | set(libs ${ARGN}) 3 | foreach(lib ${libs}) 4 | get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES) 5 | target_include_directories(${target} SYSTEM PRIVATE ${lib_include_dirs}) 6 | target_link_libraries(${target} PRIVATE ${lib}) 7 | endforeach(lib) 8 | endfunction(target_link_libraries_system) 9 | 10 | function(target_link_libraries_system_public target) 11 | set(libs ${ARGN}) 12 | foreach(lib ${libs}) 13 | get_target_property(lib_include_dirs ${lib} INTERFACE_INCLUDE_DIRECTORIES) 14 | target_include_directories(${target} SYSTEM PUBLIC ${lib_include_dirs}) 15 | target_link_libraries(${target} PUBLIC ${lib}) 16 | endforeach(lib) 17 | endfunction(target_link_libraries_system_public) 18 | 19 | macro(add_subdirectory_if_not_exists LIBRARY SUB_DIR) 20 | if(NOT TARGET ${LIBRARY}) 21 | message("-- INFO - Trying to add ${LIBRARY} from thirdparty submodule") 22 | set(LOCAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${SUB_DIR}") 23 | if (EXISTS $LOCAL_DIR) 24 | add_subdirectory("${LOCAL_DIR}") 25 | else() 26 | add_subdirectory("${PROJECT_SOURCE_DIR}/${SUB_DIR}") 27 | endif() 28 | else() 29 | message("-- INFO - Skipping add_subdirectory(${LIBRARY}) because it has already been added") 30 | endif() 31 | endmacro(add_subdirectory_if_not_exists) -------------------------------------------------------------------------------- /cmake/FindSphinx.cmake: -------------------------------------------------------------------------------- 1 | #Look for an executable called sphinx-build 2 | find_program(SPHINX_EXECUTABLE 3 | NAMES sphinx-build 4 | DOC "Path to sphinx-build executable") 5 | 6 | include(FindPackageHandleStandardArgs) 7 | #Handle standard arguments to find_package like REQUIRED and QUIET 8 | find_package_handle_standard_args(Sphinx 9 | "Failed to find sphinx-build executable" 10 | SPHINX_EXECUTABLE) -------------------------------------------------------------------------------- /cmake/GperftoolsConfig.cmake: -------------------------------------------------------------------------------- 1 | # Tries to find Gperftools. 2 | # 3 | # Usage of this module as follows: 4 | # 5 | # find_package(Gperftools) 6 | # 7 | # Variables used by this module, they can change the default behaviour and need 8 | # to be set before calling find_package: 9 | # 10 | # Gperftools_ROOT_DIR Set this variable to the root installation of Gperftools 11 | # if the module has problems finding the proper installation path. 12 | # 13 | # Variables defined by this module: 14 | # 15 | # GPERFTOOLS_FOUND System has Gperftools libs/headers 16 | # GPERFTOOLS_LIBRARIES The Gperftools libraries (tcmalloc & profiler) 17 | # GPERFTOOLS_INCLUDE_DIR The location of Gperftools headers 18 | 19 | find_library( 20 | GPERFTOOLS_TCMALLOC 21 | NAMES tcmalloc 22 | HINTS ${Gperftools_ROOT_DIR}/lib) 23 | 24 | find_library( 25 | GPERFTOOLS_PROFILER 26 | NAMES profiler 27 | HINTS ${Gperftools_ROOT_DIR}/lib) 28 | 29 | find_library( 30 | GPERFTOOLS_TCMALLOC_AND_PROFILER 31 | NAMES tcmalloc_and_profiler 32 | HINTS ${Gperftools_ROOT_DIR}/lib) 33 | 34 | find_path( 35 | GPERFTOOLS_INCLUDE_DIR 36 | NAMES gperftools/heap-profiler.h 37 | HINTS ${Gperftools_ROOT_DIR}/include) 38 | 39 | set(GPERFTOOLS_LIBRARIES ${GPERFTOOLS_TCMALLOC_AND_PROFILER}) 40 | 41 | include(FindPackageHandleStandardArgs) 42 | find_package_handle_standard_args(Gperftools DEFAULT_MSG GPERFTOOLS_LIBRARIES 43 | GPERFTOOLS_INCLUDE_DIR) 44 | 45 | mark_as_advanced( 46 | Gperftools_ROOT_DIR GPERFTOOLS_TCMALLOC GPERFTOOLS_PROFILER 47 | GPERFTOOLS_TCMALLOC_AND_PROFILER GPERFTOOLS_LIBRARIES GPERFTOOLS_INCLUDE_DIR) 48 | 49 | # create IMPORTED targets 50 | if (Gperftools_FOUND AND NOT TARGET gperftools::tcmalloc) 51 | add_library(gperftools::tcmalloc UNKNOWN IMPORTED) 52 | set_target_properties( 53 | gperftools::tcmalloc 54 | PROPERTIES 55 | IMPORTED_LOCATION ${GPERFTOOLS_TCMALLOC} INTERFACE_INCLUDE_DIRECTORIES 56 | "${GPERFTOOLS_INCLUDE_DIR}") 57 | add_library(gperftools::profiler UNKNOWN IMPORTED) 58 | set_target_properties( 59 | gperftools::profiler 60 | PROPERTIES 61 | IMPORTED_LOCATION ${GPERFTOOLS_PROFILER} INTERFACE_INCLUDE_DIRECTORIES 62 | "${GPERFTOOLS_INCLUDE_DIR}") 63 | endif () 64 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | gitpython # for version bump script 2 | pytest # for testing 3 | 4 | numpy 5 | matplotlib 6 | scipy 7 | open3d 8 | 9 | # Documentation Generation Packages 10 | notebook==5.7.10 11 | docutils>=0.17 12 | Sphinx>=4.0,<5 13 | sphinx_rtd_theme 14 | exhale==0.3.6 15 | breathe>=4.32.0 16 | m2r2==0.3.2 17 | nbsphinx==0.7.1 18 | nbsphinx-link==1.3.0 19 | Jinja2==2.11.3 20 | MarkupSafe==2.0.1 21 | 22 | # Also need to use conda to install these through conda or get binaries another way 23 | # conda install -c conda-forge pandoc doxygen -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/.nojekyll -------------------------------------------------------------------------------- /docs/_images/opc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/opc.png -------------------------------------------------------------------------------- /docs/_images/opc_mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/opc_mesh.png -------------------------------------------------------------------------------- /docs/_images/opc_polygons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/opc_polygons.png -------------------------------------------------------------------------------- /docs/_images/pl_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/pl_logo.png -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_basicdemo_14_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_basicdemo_14_0.png -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_basicdemo_17_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_basicdemo_17_0.png -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_basicdemo_21_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_basicdemo_21_1.png -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_basicdemo_6_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_basicdemo_6_1.png -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_organizeddemo_10_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_organizeddemo_10_0.png -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_organizeddemo_12_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_organizeddemo_12_0.png -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_organizeddemo_22_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_organizeddemo_22_0.png -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_organizeddemo_27_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_organizeddemo_27_0.png -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_organizeddemo_37_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_organizeddemo_37_0.png -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_organizeddemo_5_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_organizeddemo_5_0.jpg -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_organizeddemo_6_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_organizeddemo_6_1.png -------------------------------------------------------------------------------- /docs/_images/tutorial_Python_organizeddemo_7_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_images/tutorial_Python_organizeddemo_7_0.png -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/LICENSE.md: -------------------------------------------------------------------------------- 1 | This code is the fruit of Kate Morley's labor, taken from here: 2 | 3 | - http://code.iamkate.com/javascript/collapsible-lists/ 4 | 5 | She includes a generous CC0 1.0 license for all materials on her site: 6 | 7 | - http://code.iamkate.com/ 8 | -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/css/button-closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/collapsible-lists/css/button-closed.png -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/css/button-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/collapsible-lists/css/button-open.png -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/css/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/collapsible-lists/css/button.png -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/css/list-item-contents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/collapsible-lists/css/list-item-contents.png -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/css/list-item-last-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/collapsible-lists/css/list-item-last-open.png -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/css/list-item-last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/collapsible-lists/css/list-item-last.png -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/css/list-item-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/collapsible-lists/css/list-item-open.png -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/css/list-item-root.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/collapsible-lists/css/list-item-root.png -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/css/list-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/collapsible-lists/css/list-item.png -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/css/tree_view.css: -------------------------------------------------------------------------------- 1 | /* Source taken directly from: 2 | * view-source:http://code.iamkate.com/javascript/collapsible-lists/ 3 | * 4 | * Kate Morley's license for this code is CC0: 5 | * Created by [Kate Morley](http://iamkate.com/). Except where explicitly 6 | * stated otherwise, all content is released under the terms of the 7 | * [CC0 1.0 Universal legal code](http://creativecommons.org/publicdomain/zero/1.0/legalcode). 8 | */ 9 | .treeView{ 10 | -moz-user-select:none; 11 | position:relative; 12 | } 13 | 14 | .treeView ul{ 15 | margin:0 0 0 -1.5em ! important; 16 | padding:0 0 0 1.5em ! important; 17 | } 18 | 19 | .treeView ul ul{ 20 | background:url('list-item-contents.png') repeat-y left ! important; 21 | } 22 | 23 | .treeView li.lastChild > ul{ 24 | background-image:none ! important; 25 | } 26 | 27 | .treeView li{ 28 | margin:0 ! important; 29 | padding:0 ! important; 30 | background:url('list-item-root.png') no-repeat top left ! important; 31 | list-style-position:inside ! important; 32 | list-style-image:url('button.png') ! important; 33 | cursor:auto; 34 | } 35 | 36 | .treeView li.collapsibleListOpen{ 37 | list-style-image:url('button-open.png') ! important; 38 | cursor:pointer; 39 | } 40 | 41 | .treeView li.collapsibleListClosed{ 42 | list-style-image:url('button-closed.png') ! important; 43 | cursor:pointer; 44 | } 45 | 46 | .treeView li li{ 47 | background-image:url('list-item.png') ! important; 48 | padding-left:1.5em ! important; 49 | } 50 | 51 | .treeView li.lastChild{ 52 | background-image:url('list-item-last.png') ! important; 53 | } 54 | 55 | .treeView li.collapsibleListOpen{ 56 | background-image:url('list-item-open.png') ! important; 57 | } 58 | 59 | .treeView li.collapsibleListOpen.lastChild{ 60 | background-image:url('list-item-last-open.png') ! important; 61 | } 62 | -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/js/CollapsibleLists.compressed.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | CollapsibleLists.js 4 | 5 | An object allowing lists to dynamically expand and collapse 6 | 7 | Created by Kate Morley - http://code.iamkate.com/ - and released under 8 | the terms of the CC0 1.0 Universal legal code: 9 | 10 | http://creativecommons.org/publicdomain/zero/1.0/legalcode 11 | 12 | */ 13 | 14 | var CollapsibleLists=new function(){ 15 | this.apply=function(_1){ 16 | var _2=document.getElementsByTagName("ul"); 17 | for(var _3=0;_3<_2.length;_3++){ 18 | if(_2[_3].className.match(/(^| )collapsibleList( |$)/)){ 19 | this.applyTo(_2[_3],true); 20 | if(!_1){ 21 | var _4=_2[_3].getElementsByTagName("ul"); 22 | for(var _5=0;_5<_4.length;_5++){ 23 | _4[_5].className+=" collapsibleList"; 24 | } 25 | } 26 | } 27 | } 28 | }; 29 | this.applyTo=function(_6,_7){ 30 | var _8=_6.getElementsByTagName("li"); 31 | for(var _9=0;_9<_8.length;_9++){ 32 | if(!_7||_6==_8[_9].parentNode){ 33 | if(_8[_9].addEventListener){ 34 | _8[_9].addEventListener("mousedown",function(e){ 35 | e.preventDefault(); 36 | },false); 37 | }else{ 38 | _8[_9].attachEvent("onselectstart",function(){ 39 | event.returnValue=false; 40 | }); 41 | } 42 | if(_8[_9].addEventListener){ 43 | _8[_9].addEventListener("click",_a(_8[_9]),false); 44 | }else{ 45 | _8[_9].attachEvent("onclick",_a(_8[_9])); 46 | } 47 | _b(_8[_9]); 48 | } 49 | } 50 | }; 51 | function _a(_c){ 52 | return function(e){ 53 | if(!e){ 54 | e=window.event; 55 | } 56 | var _d=(e.target?e.target:e.srcElement); 57 | while(_d.nodeName!="LI"){ 58 | _d=_d.parentNode; 59 | } 60 | if(_d==_c){ 61 | _b(_c); 62 | } 63 | }; 64 | }; 65 | function _b(_e){ 66 | var _f=_e.className.match(/(^| )collapsibleListClosed( |$)/); 67 | var uls=_e.getElementsByTagName("ul"); 68 | for(var _10=0;_100){ 79 | _e.className+=" collapsibleList"+(_f?"Open":"Closed"); 80 | } 81 | }; 82 | }(); 83 | 84 | -------------------------------------------------------------------------------- /docs/_static/collapsible-lists/js/apply-collapsible-lists.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | CollapsibleLists.apply(); 3 | }); 4 | -------------------------------------------------------------------------------- /docs/_static/cpp_tutorial/opc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/cpp_tutorial/opc.png -------------------------------------------------------------------------------- /docs/_static/cpp_tutorial/opc_mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/cpp_tutorial/opc_mesh.png -------------------------------------------------------------------------------- /docs/_static/cpp_tutorial/opc_polygons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/cpp_tutorial/opc_polygons.png -------------------------------------------------------------------------------- /docs/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../fonts/fontawesome-webfont.eot");src:url("../fonts/fontawesome-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff") format("woff"),url("../fonts/fontawesome-webfont.ttf") format("truetype"),url("../fonts/fontawesome-webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} 2 | -------------------------------------------------------------------------------- /docs/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '1.0.9', 4 | LANGUAGE: 'None', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false, 12 | SHOW_SEARCH_SUMMARY: true, 13 | ENABLE_SEARCH_SHORTCUTS: true, 14 | }; -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/fonts/Inconsolata-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Inconsolata-Bold.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Inconsolata.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Inconsolata.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-bold.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-bold.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-bold.woff -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-bolditalic.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-bolditalic.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-bolditalic.woff -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-bolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-bolditalic.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-italic.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-italic.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-italic.woff -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-italic.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-regular.eot -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-regular.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-regular.woff -------------------------------------------------------------------------------- /docs/_static/fonts/Lato/lato-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/Lato/lato-regular.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/RobotoSlab-Bold.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/RobotoSlab-Regular.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff -------------------------------------------------------------------------------- /docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/docs/_static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | /* sphinx_rtd_theme version 0.4.3 | MIT license */ 2 | /* Built 20190212 16:02 */ 3 | require=function r(s,a,l){function c(e,n){if(!a[e]){if(!s[e]){var i="function"==typeof require&&require;if(!n&&i)return i(e,!0);if(u)return u(e,!0);var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}var o=a[e]={exports:{}};s[e][0].call(o.exports,function(n){return c(s[e][1][n]||n)},o,o.exports,r,s,a,l)}return a[e].exports}for(var u="function"==typeof require&&require,n=0;n"),i("table.docutils.footnote").wrap("
"),i("table.docutils.citation").wrap("
"),i(".wy-menu-vertical ul").not(".simple").siblings("a").each(function(){var e=i(this);expand=i(''),expand.on("click",function(n){return t.toggleCurrent(e),n.stopPropagation(),!1}),e.prepend(expand)})},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),i=e.find('[href="'+n+'"]');if(0===i.length){var t=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(i=e.find('[href="#'+t.attr("id")+'"]')).length&&(i=e.find('[href="#"]'))}0this.docHeight||(this.navBar.scrollTop(i),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",function(){this.linkScroll=!1})},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:e.exports.ThemeNav,StickyNav:e.exports.ThemeNav}),function(){for(var r=0,n=["ms","moz","webkit","o"],e=0;e -------------------------------------------------------------------------------- /examples/cpp/debug.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Polylidar/Polylidar.hpp" 3 | #include "Polylidar/Delaunator/Delaunator.hpp" 4 | #include "npy/npy.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace Polylidar; 11 | namespace o3d = open3d; 12 | using TriMesh = MeshHelper::HalfEdgeTriangulation; 13 | 14 | void PrintPoint(Polylidar::Delaunator::Delaunator &mesh, size_t idx) 15 | { 16 | 17 | std:: cout << "[" << mesh.triangles(idx, 0) << ", " << mesh.triangles(idx, 1) << ", " << mesh.triangles(idx, 2) << "]" << std::endl; 18 | } 19 | 20 | int main(int argc, char const *argv[]) 21 | { 22 | const std::string SPARSE_MESH = "./fixtures/meshes/sparse_basement.ply"; 23 | Eigen::Matrix3d mesh_rotation; 24 | std::shared_ptr sparse_mesh_o3d; 25 | 26 | // Rotation for mesh, camera axis to global frame 27 | mesh_rotation << 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0; 28 | // Sparse Mesh In O3D Format 29 | sparse_mesh_o3d = o3d::io::CreateMeshFromFile(SPARSE_MESH); 30 | sparse_mesh_o3d->Rotate(mesh_rotation); 31 | // sparse_mesh_o3d->Rotate(); 32 | // Sparse Mesh In HalfEdgeTriangulation 33 | auto triangles_ptr = reinterpret_cast(sparse_mesh_o3d->triangles_.data()); 34 | auto num_triangles = sparse_mesh_o3d->triangles_.size(); 35 | auto vertices_ptr = reinterpret_cast(sparse_mesh_o3d->vertices_.data()); 36 | auto num_vertices = sparse_mesh_o3d->vertices_.size(); 37 | Matrix triangles(triangles_ptr, num_triangles, 3); 38 | Matrix vertices(vertices_ptr, num_vertices, 3); 39 | 40 | TriMesh sparse_mesh = MeshHelper::CreateTriMeshCopy(vertices, triangles, true); 41 | 42 | // MeshHelper::BilateralFilterNormals(sparse_mesh, 1, 0.1, 0.1); 43 | // alpha, lmax, min_tri, min_hole_vert, z_thresh, norm__thresh, norm_thresh_min, _task_threads 44 | Polylidar3D pl3d(0.0, 0.1, 1000, 6, 0.03, 0.95, 0.90); 45 | auto planes_and_polygons = pl3d.ExtractPlanesAndPolygonsOptimized(sparse_mesh, {{0.0, 0.0, 1.0}}); 46 | std::cout << "Stop Here"; 47 | 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /examples/cpp/simple.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include "Polylidar/Polylidar.hpp" 5 | 6 | template 7 | std::ostream& operator<<(std::ostream& os, const std::vector& vec) 8 | { 9 | auto iter_begin = vec.begin(); 10 | auto iter_end = vec.end(); 11 | os << "["; 12 | for (auto iter = iter_begin; iter != iter_end; ++iter) 13 | { 14 | std::cout << ((iter != iter_begin) ? "," : "") << *iter; 15 | } 16 | os << "]"; 17 | return os; 18 | } 19 | int main(int argc, char const* argv[]) 20 | { 21 | std::cout << "Very Simple C++ Example of Polylidar3D on 2D Point Set" << std::endl; 22 | std::vector points_data = { 23 | 0.0, 0.0, // Point index 0 24 | 0.0, 1.0, // Point index 1 25 | 1.0, 1.0, // Point index 2 26 | 1.0, 0.0, // Point index 3 27 | 5.0, 0.1, // Point index 4, outlier and should not be included in polygon 28 | }; 29 | 30 | // 5 X 2 matrix as one contigious array 31 | std::vector shape = {points_data.size() / 2, 2}; 32 | Polylidar::Matrix points(points_data.data(), shape[0], shape[1]); 33 | // Set configuration parameters 34 | 35 | // alpha, lmax, min triangles, min hole vertices, 36 | Polylidar::Polylidar3D pl(0.0, 2.0, 1, 3); 37 | 38 | Polylidar::MeshHelper::HalfEdgeTriangulation mesh; 39 | Polylidar::Planes planes; 40 | Polylidar::Polygons polygons; 41 | auto before = std::chrono::high_resolution_clock::now(); 42 | std:tie(mesh, planes, polygons) = pl.ExtractPlanesAndPolygons(points); 43 | auto after = std::chrono::high_resolution_clock::now(); 44 | 45 | auto elapsed = std::chrono::duration_cast(after - before); 46 | std::cout << "Polylidar took " << elapsed.count() << " milliseconds processing a " << shape[0] << " point cloud" 47 | << std::endl; 48 | std::cout << "Point indices of Polygon Shell: "; 49 | // Extract polygon 50 | for (auto const& polygon : polygons) 51 | { 52 | std::cout << polygon.shell << std::endl; 53 | } 54 | // Point indices of Polygon Shell: [3,0,1,2] 55 | 56 | std::cout << std::endl; 57 | 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /examples/cpp/vis_util.hpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "FastGA/FastGA.hpp" 4 | #include "open3d/Open3D.h" 5 | namespace VisUtility { 6 | 7 | FastGA::MatX3d colors_palette{{0.12156862745098039, 0.4666666666666667, 0.7058823529411765}, 8 | {1.0, 0.4980392156862745, 0.054901960784313725}, 9 | {0.17254901960784313, 0.6274509803921569, 0.17254901960784313}, 10 | {0.8392156862745098, 0.15294117647058825, 0.1568627450980392}, 11 | {0.5803921568627451, 0.403921568627451, 0.7411764705882353}, 12 | {0.5490196078431373, 0.33725490196078434, 0.29411764705882354}, 13 | {0.8901960784313725, 0.4666666666666667, 0.7607843137254902}, 14 | {0.4980392156862745, 0.4980392156862745, 0.4980392156862745}, 15 | {0.7372549019607844, 0.7411764705882353, 0.13333333333333333}, 16 | {0.09019607843137255, 0.7450980392156863, 0.8117647058823529}}; 17 | 18 | int color_counter = 0; 19 | 20 | std::array NextColor() 21 | { 22 | auto color = colors_palette[color_counter]; 23 | color_counter++; 24 | if (color_counter >= colors_palette.size()) 25 | { 26 | color_counter = 0; 27 | } 28 | return color; 29 | } 30 | 31 | template 32 | std::vector MapEigen(Polylidar::Matrix& array, const bool set_nan_to_zero = true) 33 | { 34 | std::vector eigen_vectors; 35 | using TO_T = eigen_vector::Scalar; 36 | for (auto i = 0; i < array.rows; ++i) 37 | { 38 | // Open3D Point cloud can't handle Nans 39 | if (set_nan_to_zero && std::isnan(static_cast(array(i, 0)))) 40 | { 41 | eigen_vectors.emplace_back(static_cast(0), static_cast(0), static_cast(0)); 42 | } 43 | else 44 | { 45 | eigen_vectors.emplace_back(static_cast(array(i, 0)), static_cast(array(i, 1)), 46 | static_cast(array(i, 2))); 47 | } 48 | } 49 | return eigen_vectors; 50 | } 51 | 52 | std::shared_ptr CreateOpen3DMesh(Polylidar::MeshHelper::HalfEdgeTriangulation& tri_mesh) 53 | { 54 | std::shared_ptr triangle_ptr(new open3d::geometry::TriangleMesh()); 55 | triangle_ptr->vertices_ = MapEigen(tri_mesh.vertices); 56 | triangle_ptr->triangles_ = MapEigen(tri_mesh.triangles, false); 57 | triangle_ptr->triangle_normals_ = MapEigen(tri_mesh.triangle_normals); 58 | triangle_ptr->ComputeVertexNormals(); 59 | triangle_ptr->PaintUniformColor({0.5, 0.5, 0.5}); 60 | return triangle_ptr; 61 | } 62 | 63 | void PaintPlane(std::shared_ptr o3d_mesh, std::vector& segment, 64 | std::array color) 65 | { 66 | auto& triangles = o3d_mesh->triangles_; 67 | Eigen::Vector3d color_e(color[0], color[1], color[2]); 68 | for (auto& t : segment) 69 | { 70 | auto vertices_idx = triangles[t]; 71 | o3d_mesh->vertex_colors_[vertices_idx[0]] = color_e; 72 | o3d_mesh->vertex_colors_[vertices_idx[1]] = color_e; 73 | o3d_mesh->vertex_colors_[vertices_idx[2]] = color_e; 74 | } 75 | } 76 | 77 | std::shared_ptr CreateLineSetFromPolygon(Polylidar::Polygon& polygon, 78 | Polylidar::Matrix points, 79 | Eigen::Vector3d shell_color = {0.0, 1.0, 0.0}, 80 | Eigen::Vector3d hole_color = {1.0, 0.0, 0.0}) 81 | { 82 | std::vector line_points; 83 | std::vector lines; 84 | std::vector colors; 85 | 86 | std::shared_ptr ls_ptr(new open3d::geometry::LineSet()); 87 | for (int i = 0; i < polygon.shell.size(); ++i) 88 | { 89 | auto idx = polygon.shell[i]; 90 | Eigen::Vector3d line_point(points(idx, 0), points(idx, 1), points(idx, 2)); 91 | line_points.push_back(line_point); 92 | int cur_point = static_cast(line_points.size() - 1); 93 | colors.push_back(shell_color); 94 | if (i != polygon.shell.size() - 1) 95 | { 96 | lines.push_back({cur_point, cur_point + 1}); 97 | colors.push_back(shell_color); 98 | } 99 | } 100 | for (auto& hole : polygon.holes) 101 | { 102 | for (int i = 0; i < hole.size(); ++i) 103 | { 104 | auto idx = hole[i]; 105 | Eigen::Vector3d line_point(points(idx, 0), points(idx, 1), points(idx, 2)); 106 | line_points.push_back(line_point); 107 | int cur_point = static_cast(line_points.size() - 1); 108 | colors.push_back(hole_color); 109 | if (i != hole.size() - 1) 110 | { 111 | lines.push_back({cur_point, cur_point + 1}); 112 | colors.push_back(hole_color); 113 | } 114 | } 115 | } 116 | ls_ptr->points_ = line_points; 117 | ls_ptr->lines_ = lines; 118 | ls_ptr->colors_ = colors; 119 | return ls_ptr; 120 | } 121 | 122 | } // namespace VisUtility -------------------------------------------------------------------------------- /examples/python/basic25d.py: -------------------------------------------------------------------------------- 1 | """Example MultiPolygon Extraction with an **Unorganized* 3D Point Cloud 2 | Note this method is only suitable if you know the dominant surface normal of the plane you desire to extract. 3 | The 3D point cloud must be rotated such that this surface normal is [0,0,1], i.e., the plane is aligned with the XY Plane 4 | The method relies upon 3D->2D Projection and performs 2.5D Delaunay Triangulation. 5 | """ 6 | import time 7 | import math 8 | 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | from mpl_toolkits.mplot3d import Axes3D 12 | 13 | 14 | from polylidar import MatrixDouble, Polylidar3D 15 | from polylidar.polylidarutil import (plot_polygons_3d, generate_3d_plane, set_axes_equal, plot_planes_3d, 16 | scale_points, rotation_matrix, apply_rotation) 17 | 18 | 19 | def main(): 20 | np.random.seed(1) 21 | # generate random plane with hole 22 | plane = generate_3d_plane(bounds_x=[0, 10, 0.5], bounds_y=[0, 10, 0.5], holes=[ 23 | [[3, 5], [3, 5]]], height_noise=0.02, planar_noise=0.02) 24 | # Generate top of box (causing the hole that we see) 25 | box_top = generate_3d_plane(bounds_x=[3, 5, 0.2], bounds_y=[3, 5, 0.2], holes=[ 26 | ], height_noise=0.02, height=2, planar_noise=0.02) 27 | # Generate side of box (causing the hole that we see) 28 | box_side = generate_3d_plane(bounds_x=[0, 2, 0.2], bounds_y=[ 29 | 0, 2, 0.2], holes=[], height_noise=0.02, planar_noise=0.02) 30 | rm = rotation_matrix([0, 1, 0], -math.pi / 2.0) 31 | box_side = apply_rotation(rm, box_side) + [5, 3, 0] 32 | # All points joined together 33 | points = np.concatenate((plane, box_side, box_top)) 34 | 35 | points_mat = MatrixDouble(points) 36 | polylidar_kwargs = dict(alpha=0.0, lmax=1.0, min_triangles=20, z_thresh=0.1, norm_thresh_min=0.94) 37 | polylidar = Polylidar3D(**polylidar_kwargs) 38 | 39 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 40 | subplot_kw=dict(projection='3d')) 41 | # plot points 42 | ax.scatter(*scale_points(points), s=2.5, c=points[:, 2], cmap=plt.cm.plasma) 43 | set_axes_equal(ax) 44 | ax.view_init(elev=15., azim=-35) 45 | plt.show() 46 | 47 | # Extracts planes and polygons, time 48 | t1 = time.time() 49 | mesh, planes, polygons = polylidar.extract_planes_and_polygons(points_mat) 50 | t2 = time.time() 51 | print("Took {:.2f} milliseconds".format((t2 - t1) * 1000)) 52 | print("Should see two planes extracted, please rotate.") 53 | 54 | triangles = np.asarray(mesh.triangles) 55 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 56 | subplot_kw=dict(projection='3d')) 57 | # plot all triangles 58 | plot_planes_3d(points, triangles, planes, ax) 59 | plot_polygons_3d(points, polygons, ax) 60 | # plot points 61 | ax.scatter(*scale_points(points), c='k', s=0.1) 62 | set_axes_equal(ax) 63 | ax.view_init(elev=15., azim=-35) 64 | plt.show() 65 | print("") 66 | 67 | 68 | if __name__ == "__main__": 69 | main() 70 | -------------------------------------------------------------------------------- /examples/python/basic25d_algorithm.py: -------------------------------------------------------------------------------- 1 | """Example MultiPolygon Extraction with an **Unorganized* 3D Point Cloud. Step through algorithm. 2 | Note this method is only suitable if you know the dominant surface normal of the plane you desire to extract. 3 | The 3D point cloud must be rotated such that this surface normal is [0,0,1], i.e., the plane is aligned with the XY Plane 4 | The method relies upon 3D->2D Projection and performs 2.5D Delaunay Triangulation. 5 | """ 6 | import time 7 | import math 8 | 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | from mpl_toolkits.mplot3d import Axes3D 12 | 13 | 14 | from polylidar import MatrixDouble, Polylidar3D 15 | from polylidar.polylidarutil import (plot_polygons_3d, generate_3d_plane, set_axes_equal, plot_planes_3d, 16 | scale_points, rotation_matrix, apply_rotation) 17 | 18 | 19 | def main(): 20 | np.random.seed(1) 21 | # generate random plane with hole 22 | plane = generate_3d_plane(bounds_x=[0, 10, 0.5], bounds_y=[0, 10, 0.5], holes=[ 23 | [[3, 5], [3, 5]]], height_noise=0.02, planar_noise=0.02) 24 | # Generate top of box (causing the hole that we see) 25 | box_top = generate_3d_plane(bounds_x=[3, 5, 0.2], bounds_y=[3, 5, 0.2], holes=[ 26 | ], height_noise=0.02, height=2, planar_noise=0.02) 27 | # Generate side of box (causing the hole that we see) 28 | box_side = generate_3d_plane(bounds_x=[0, 2, 0.2], bounds_y=[ 29 | 0, 2, 0.2], holes=[], height_noise=0.02, planar_noise=0.02) 30 | rm = rotation_matrix([0, 1, 0], -math.pi / 2.0) 31 | box_side = apply_rotation(rm, box_side) + [5, 3, 0] 32 | # All points joined together 33 | points = np.concatenate((plane, box_side, box_top)) 34 | 35 | points_mat = MatrixDouble(points) 36 | polylidar_kwargs = dict(alpha=0.0, lmax=1.0, min_triangles=20, z_thresh=0.1, norm_thresh_min=0.94) 37 | polylidar = Polylidar3D(**polylidar_kwargs) 38 | 39 | elev = 15.0 40 | azim = -35 41 | 42 | # Show Point Cloud 43 | print("Should see point raw point cloud") 44 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 45 | subplot_kw=dict(projection='3d')) 46 | # plot points 47 | ax.scatter(*scale_points(points), s=20.0, c=points[:, 2], cmap=plt.cm.plasma) 48 | set_axes_equal(ax) 49 | ax.view_init(elev=elev, azim=azim) 50 | fig.savefig("assets/scratch/Basic25DAlgorithm_pointcloud.pdf", bbox_inches='tight') 51 | fig.savefig("assets/scratch/Basic25DAlgorithm_pointcloud.png", bbox_inches='tight', pad_inches=-0.8) 52 | plt.show() 53 | 54 | # Extracts planes and polygons, time 55 | t1 = time.time() 56 | mesh, planes, polygons = polylidar.extract_planes_and_polygons(points_mat) 57 | t2 = time.time() 58 | print("Polylidar Took {:.2f} milliseconds".format((t2 - t1) * 1000)) 59 | 60 | triangles = np.asarray(mesh.triangles) 61 | all_planes = [np.arange(triangles.shape[0])] 62 | 63 | # Show Triangulation 64 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 65 | subplot_kw=dict(projection='3d')) 66 | 67 | plot_planes_3d(points, triangles, all_planes, ax, alpha=0.0, z_value=-4.0) 68 | plot_planes_3d(points, triangles, all_planes, ax, alpha=0.5) 69 | # plot points 70 | ax.scatter(*scale_points(points), s=0.1, c='k') 71 | set_axes_equal(ax, ignore_z=True) 72 | ax.set_zlim3d([-4, 6]) 73 | ax.view_init(elev=elev, azim=azim) 74 | print("Should see triangulation point cloud") 75 | fig.savefig("assets/scratch/Basic25DAlgorithm_mesh.pdf", bbox_inches='tight') 76 | fig.savefig("assets/scratch/Basic25DAlgorithm_mesh.png", bbox_inches='tight', pad_inches=-0.8) 77 | plt.show() 78 | 79 | print("") 80 | print("Should see two planes extracted, please rotate.") 81 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 82 | subplot_kw=dict(projection='3d')) 83 | # plot all triangles 84 | plot_polygons_3d(points, polygons, ax) 85 | plot_planes_3d(points, triangles, planes, ax) 86 | # plot points 87 | ax.scatter(*scale_points(points), c='k', s=0.1) 88 | set_axes_equal(ax) 89 | ax.view_init(elev=elev, azim=azim) 90 | fig.savefig("assets/scratch/Basic25DAlgorithm_polygons.pdf", bbox_inches='tight') 91 | fig.savefig("assets/scratch/Basic25DAlgorithm_polygons.png", bbox_inches='tight', pad_inches=-0.8) 92 | plt.show() 93 | print("") 94 | 95 | 96 | if __name__ == "__main__": 97 | main() 98 | -------------------------------------------------------------------------------- /examples/python/basic2d.py: -------------------------------------------------------------------------------- 1 | """Example of extracting MultiPolygons with holes of a 2D point set 2 | """ 3 | import time 4 | 5 | import numpy as np 6 | from polylidar import MatrixDouble, Polylidar3D 7 | from polylidar.polylidarutil import (generate_test_points, plot_triangles, get_estimated_lmax, 8 | plot_triangle_meshes, get_triangles_from_list, get_colored_planar_segments, plot_polygons) 9 | import matplotlib.pyplot as plt 10 | 11 | np.random.seed(1) 12 | 13 | 14 | def main(): 15 | kwargs = dict(num_groups=2, group_size=1000, dist=100.0, seed=1) 16 | # generate 2 random normally distributed clusters of points, 200 X 2 numpy array. 17 | points = generate_test_points(**kwargs) 18 | lmax = get_estimated_lmax(**kwargs) 19 | polylidar_kwargs = dict(alpha=0.0, lmax=lmax, min_triangles=5) 20 | 21 | # Convert points to matrix format (no copy) and make Polylidar3D Object 22 | points_mat = MatrixDouble(points, copy=False) 23 | polylidar = Polylidar3D(**polylidar_kwargs) 24 | 25 | # Extract the mesh, planes, polygons, and time 26 | t1 = time.perf_counter() 27 | mesh, planes, polygons = polylidar.extract_planes_and_polygons(points_mat) 28 | t2 = time.perf_counter() 29 | 30 | print("Took {:.2f} milliseconds".format((t2 - t1) * 1000)) 31 | 32 | # Convert to numpy format, no copy with np.asarray() 33 | triangles = np.asarray(mesh.triangles) 34 | 35 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1) 36 | # plot points 37 | ax.scatter(points[:, 0], points[:, 1], c='k') 38 | # plot all triangles 39 | # plt.triplot(points[:,0], points[:,1], triangles) # better alternative 40 | plot_triangles(get_triangles_from_list(triangles, points), ax) 41 | # plot seperated planar triangular segments 42 | triangle_meshes = get_colored_planar_segments(planes, triangles, points) 43 | plot_triangle_meshes(triangle_meshes, ax) 44 | # plot polygons 45 | plot_polygons(polygons, points, ax) 46 | plt.axis('equal') 47 | plt.show() 48 | 49 | 50 | if __name__ == "__main__": 51 | main() 52 | -------------------------------------------------------------------------------- /examples/python/basic2d_algorithm.py: -------------------------------------------------------------------------------- 1 | """Example of extracting MultiPolygons with holes of a 2D point set. Step through algorithm. 2 | """ 3 | import time 4 | import numpy as np 5 | from polylidar import Delaunator, MatrixDouble, Polylidar3D 6 | from polylidar.polylidarutil import (generate_test_points, plot_points, plot_triangles, get_estimated_lmax, 7 | plot_triangle_meshes, get_triangles_from_list, get_colored_planar_segments, plot_polygons) 8 | import matplotlib.pyplot as plt 9 | 10 | np.random.seed(1) 11 | 12 | def get_np_buffer_ptr(a): 13 | pointer, read_only_flag = a.__array_interface__['data'] 14 | return pointer 15 | 16 | def main(): 17 | 18 | kwargs = dict(num_groups=2, group_size=1000, dist=100.0, seed=1) 19 | # generate random normally distributed clusters of points, 200 X 2 numpy array. 20 | points = generate_test_points(**kwargs) 21 | lmax = get_estimated_lmax(**kwargs) 22 | polylidar_kwargs = dict(alpha=0.0, lmax=lmax, min_triangles=5) 23 | # print(polylidar_kwargs) 24 | # Convert Points and make Polylidar 25 | points_mat = MatrixDouble(points) 26 | polylidar = Polylidar3D(**polylidar_kwargs) 27 | # Extracts planes and polygons, time 28 | t1 = time.perf_counter() 29 | mesh, planes, polygons = polylidar.extract_planes_and_polygons(points_mat) 30 | t2 = time.perf_counter() 31 | triangles = np.asarray(mesh.triangles) 32 | # print(triangles, triangles.shape) 33 | triangles = triangles.flatten() 34 | # print(triangles, triangles.shape) 35 | planes_np = np.asarray(planes) 36 | # print(planes_np) 37 | print("Took {:.2f} milliseconds".format((t2 - t1) * 1000)) 38 | 39 | # Plot Data 40 | if points.shape[0] < 100000: 41 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1) 42 | # plot points 43 | print("Plot Points") 44 | ax.scatter(points[:, 0], points[:, 1], c='k', s=20.0) 45 | fig.savefig("assets/scratch/Basic2DAlgorithm_pointcloud.png", bbox_inches='tight', pad_inches=-0.6) 46 | plt.show() 47 | 48 | print("Plot Mesh") 49 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1) 50 | # plot points 51 | ax.scatter(points[:, 0], points[:, 1], c='k', s=0.1) 52 | # plot all triangles 53 | plot_triangles(get_triangles_from_list(triangles, points), ax) 54 | fig.savefig("assets/scratch/Basic2DAlgorithm_mesh.png", bbox_inches='tight', pad_inches=-0.6) 55 | plt.show() 56 | 57 | print("Planes and Polygons") 58 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1) 59 | # plot points 60 | ax.scatter(points[:, 0], points[:, 1], c='k', s=0.1) 61 | # plot all triangles 62 | plot_triangles(get_triangles_from_list(triangles, points), ax) 63 | # plot mesh triangles 64 | triangle_meshes = get_colored_planar_segments(planes_np, triangles, points) 65 | plot_triangle_meshes(triangle_meshes, ax) 66 | # plot polygons 67 | plot_polygons(polygons, points, ax) 68 | 69 | plt.axis('equal') 70 | 71 | plt.show() 72 | 73 | if __name__ == "__main__": 74 | main() -------------------------------------------------------------------------------- /examples/python/bilateral.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple example demonstrating a bilateral filter implented in C++. 3 | Note that this is NOT the accelerated bilateral filter discussed in the Paper. This is just something fun I tried out that works for generalized meshes. 4 | The accelerated bilateral filter for **organized** point clouds is found in OrganizedPointFilters repo (not this repo). 5 | Requires: Fixtures data 6 | """ 7 | import time 8 | import logging 9 | import warnings 10 | 11 | import numpy as np 12 | from scipy.spatial.transform import Rotation as R 13 | import open3d as o3d 14 | 15 | 16 | from examples.python.util.mesh_util import get_mesh_data_iterator 17 | from polylidar import bilateral_filter_normals 18 | from polylidar.polylidarutil.open3d_util import open_3d_mesh_to_trimesh 19 | 20 | def main(): 21 | for i, mesh in enumerate(get_mesh_data_iterator()): 22 | if i < 0: 23 | continue 24 | mesh.compute_vertex_normals() 25 | mesh.compute_triangle_normals() 26 | print("Before") 27 | o3d.visualization.draw_geometries([mesh]) 28 | 29 | tri_mesh = open_3d_mesh_to_trimesh(mesh) 30 | t1 = time.perf_counter() 31 | bilateral_filter_normals(tri_mesh, iterations=20, sigma_length=0.1, sigma_angle=0.1) 32 | t2 = time.perf_counter() 33 | print(t2-t1) 34 | normals_smooth = np.asarray(tri_mesh.triangle_normals) 35 | mesh.triangle_normals = o3d.utility.Vector3dVector(normals_smooth) 36 | 37 | print("After") 38 | o3d.visualization.draw_geometries([mesh]) 39 | 40 | 41 | 42 | if __name__ == "__main__": 43 | main() 44 | -------------------------------------------------------------------------------- /examples/python/extras/test_delaunay.py: -------------------------------------------------------------------------------- 1 | """Used to just test Delaunay triangulation 2 | """ 3 | import numpy as np 4 | from polylidar import Delaunator, MatrixDouble 5 | import matplotlib.pyplot as plt 6 | 7 | def main(): 8 | points = np.random.randn(1000, 2) 9 | 10 | # Use Delaunay Triangulation 11 | points_mat = MatrixDouble(points) 12 | delaunay = Delaunator(points_mat) 13 | delaunay.triangulate() 14 | triangles = np.copy(np.asarray(delaunay.triangles)) 15 | 16 | plt.triplot(points[:,0], points[:,1], triangles) 17 | 18 | plt.scatter(points[:, 0], points[: ,1]) 19 | plt.show() 20 | 21 | if __name__ == "__main__": 22 | main() -------------------------------------------------------------------------------- /examples/python/extras/test_matrix.py: -------------------------------------------------------------------------------- 1 | """Used to test Polyidars Matrix Class 2 | This test ensures that a no copy operation actually works 3 | """ 4 | import numpy as np 5 | from polylidar import Polylidar3D, MatrixDouble 6 | 7 | def get_np_buffer_ptr(a): 8 | pointer, read_only_flag = a.__array_interface__['data'] 9 | return pointer 10 | 11 | def main(): 12 | a = np.arange(4, dtype=np.float64).reshape((2,2)) 13 | print("Numpy Array and Buffer Ptr") 14 | print(a) 15 | print(get_np_buffer_ptr(a)) 16 | b = MatrixDouble(a, False) 17 | c = np.asarray(b) 18 | print("") 19 | print("Matrix Array and Buffer Ptr") 20 | print(c) 21 | print(get_np_buffer_ptr(c)) 22 | try: 23 | 24 | d = a.reshape((4,)) 25 | print("") 26 | print("Reshape to 1 Dimension") 27 | print(d) 28 | print("Should throw runtime error because not 2 Dim") 29 | e = MatrixDouble(d, False) 30 | except Exception: 31 | print("Successfully threw an exception") 32 | 33 | 34 | 35 | if __name__ == "__main__": 36 | main() -------------------------------------------------------------------------------- /examples/python/extras/test_o3d.py: -------------------------------------------------------------------------------- 1 | """Demo of Open3D 0.10.0 Slowdown 2 | Please modify DIRECTORY to point to the folder of meshes attached to this issue reply 3 | """ 4 | import os 5 | import open3d as o3d 6 | import copy 7 | DIRECTORY = 'fixtures/o3d_slow_down' 8 | # o3d 0.10.0 - 9 Seconds to load meshes (time to being user interation), 1 FPS (with draw edges enabled 'w') 9 | # o3d 0.11.0+f1d478c4 - 0.5 seconds, 45-60 FPS (with draw edges) 10 | 11 | duplicate = 50 12 | def main(): 13 | all_meshes = [] 14 | all_files = sorted(list(os.listdir(DIRECTORY))) 15 | for filename in all_files: 16 | print(filename) 17 | mesh = o3d.io.read_triangle_mesh(os.path.join(DIRECTORY, filename)) 18 | 19 | all_meshes.extend([ copy.deepcopy(mesh) for i in range(duplicate)]) 20 | print(len(all_meshes)) 21 | o3d.visualization.draw_geometries(all_meshes) 22 | 23 | if __name__ == "__main__": 24 | main() -------------------------------------------------------------------------------- /examples/python/for_landing/PolylidarParams.yaml: -------------------------------------------------------------------------------- 1 | mesh: 2 | use_cuda: true # use GPU CUDA acceleration for mesh smoothing 3 | stride: 1 # skip rows/columns 4 | filter: 5 | loops_laplacian: 1 # how many iterations 6 | _lambda: 0.65 # weight factor for laplacian update 7 | kernel_size: 3 # only changes for laplacian 8 | loops_bilateral: 4 # how many iterations 9 | sigma_length: 0.3 # std of distance between triangles centroids 10 | sigma_angle: 0.2 # std of distance between triangles normals 11 | polylidar: # Parameters we send to polylidar. Determine plane and polygon extraction from point clouds. 12 | alpha: 0.0 # must be set to 0.0 if using lmax 13 | lmax: 1.5 # maximum distance between points in plane for spatial connection 14 | z_thresh: 0.2 # enforce point to plane distance constraints during region growing. 15 | norm_thresh: 0.90 # Not used, set to the same as norm_thresh_min. Will deprecate later. 16 | norm_thresh_min: 0.96 # triangles must have a minimum amount of planarity. 17 | min_hole_vertices : 4 # minimum number of vertices in a hole to return 18 | min_triangles: 500 # minimum number of triangles needed to make a plane 19 | fastga: # Parameters used for dominant plane normal estimation 20 | level: 5 # refinement level of the gaussian accumulator 21 | down_sample_fraction: 0.50 # only use X% of triangle normals from mesh for integration, lower the faster 22 | find_peaks_kwargs: # peak detection arguments 23 | threshold_abs: 50 # [0-255], minimum value of normalized histogram of S2 to be a peak 24 | min_distance: 1 # 1 = 3X3 kernel for peak detector. I recommend to not change 25 | exclude_border: true 26 | indices: false # must return mask 27 | cluster_kwargs: # Agglomerative hierarchal clustering 28 | t: 0.28 # min distance in 3D space of peaks (surface normals on sphere) before merging 29 | criterion: 'distance' 30 | average_filter: # A merge group must have at least x% of value in all of histogram, this doesn't have much meaning and will probably be deprecated 31 | min_total_weight: 0.1 32 | polygon: 33 | postprocess: # post processing of polygons returned from polylidar for ground/obstacle identification 34 | filter: # obstacles must have these characteristics 35 | hole_area: 36 | min: 0.10 # m^2 37 | max: 80.0 # m^2 38 | hole_vertices: 39 | min: 4 40 | plane_area: 41 | min: 4 # m^2 42 | max: 400 43 | # These parameters correspond to Shapely polygon geometry operations 44 | positive_buffer: 0.1 # m, Positively expand polygon. Fills in small holes 45 | negative_buffer: 0.25 # m, Negative buffer to polygon. Expands holes and constricts outer hull of polygon 46 | simplify: 0.25 # m, simplify edges of polygon 47 | polylabel: 48 | precision: 0.1 -------------------------------------------------------------------------------- /examples/python/for_landing/PolylidarParamsSimple.yaml: -------------------------------------------------------------------------------- 1 | mesh: 2 | use_cuda: true # use GPU CUDA acceleration for mesh smoothing 3 | stride: 1 # skip rows/columns 4 | filter: 5 | loops_laplacian: 1 # how many iterations 6 | _lambda: 0.65 # weight factor for laplacian update 7 | kernel_size: 3 # only changes for laplacian 8 | loops_bilateral: 4 # how many iterations 9 | sigma_length: 0.3 # std of distance between triangles centroids 10 | sigma_angle: 0.2 # std of distance between triangles normals 11 | polylidar: # Parameters we send to polylidar. Determine plane and polygon extraction from point clouds. 12 | alpha: 0.0 # must be set to 0.0 if using lmax 13 | lmax: 2.5 # maximum distance between points in plane for spatial connection 14 | z_thresh: 0.2 # enforce point to plane distance constraints during region growing. 15 | norm_thresh: 0.96 # Not used, set to the same as norm_thresh_min. Will deprecate later. 16 | norm_thresh_min: 0.96 # triangles must have a minimum amount of planarity. 17 | min_hole_vertices: 4 # minimum number of vertices in a hole to return 18 | min_triangles: 100 # minimum number of triangles needed to make a plane 19 | fastga: # Parameters used for dominant plane normal estimation 20 | level: 5 # refinement level of the gaussian accumulator 21 | down_sample_fraction: 0.50 # only use X% of triangle normals from mesh for integration, lower the faster 22 | find_peaks_kwargs: # peak detection arguments 23 | threshold_abs: 50 # [0-255], minimum value of normalized histogram of S2 to be a peak 24 | min_distance: 1 # 1 = 3X3 kernel for peak detector. I recommend to not change 25 | exclude_border: true 26 | indices: false # must return mask 27 | cluster_kwargs: # Agglomerative hierarchal clustering 28 | t: 0.28 # min distance in 3D space of peaks (surface normals on sphere) before merging 29 | criterion: "distance" 30 | average_filter: # A merge group must have at least x% of value in all of histogram, this doesn't have much meaning and will probably be deprecated 31 | min_total_weight: 0.1 32 | polygon: 33 | postprocess: # post processing of polygons returned from polylidar for ground/obstacle identification 34 | filter: # obstacles must have these characteristics 35 | hole_area: 36 | min: 0.10 # m^2 37 | max: 10000.0 # m^2 38 | hole_vertices: 39 | min: 4 40 | plane_area: 41 | min: 4 # m^2 42 | max: 400 43 | # These parameters correspond to Shapely polygon geometry operations 44 | positive_buffer: 0.1 # m, Positively expand polygon. Fills in small holes 45 | negative_buffer: 0.2 # m, Negative buffer to polygon. Expands holes and constricts outer hull of polygon 46 | simplify: 0.3 # m, simplify edges of polygon 47 | -------------------------------------------------------------------------------- /examples/python/for_landing/basic_rooftop_classification.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | import sys 4 | import functools 5 | import operator 6 | from pathlib import Path 7 | 8 | import numpy as np 9 | import open3d as o3d 10 | import matplotlib.cm as cm 11 | import yaml 12 | 13 | 14 | from polylidar import (Polylidar3D, MatrixDouble, MatrixFloat, extract_tri_mesh_from_float_depth, 15 | extract_point_cloud_from_float_depth, MatrixUInt8) 16 | 17 | from polylidar.polylidarutil.open3d_util import construct_grid, create_lines, flatten, create_open_3d_mesh_from_tri_mesh 18 | from polylidar.polylidarutil.plane_filtering import filter_planes_and_holes 19 | from polylidar.polylidarutil.line_mesh import LineMesh 20 | 21 | from organizedpointfilters.utility.helper import create_meshes_cuda 22 | from fastga import GaussianAccumulatorS2Beta, IcoCharts 23 | 24 | from examples.python.util.helper_polylidar import extract_all_dominant_plane_normals, extract_planes_and_polygons_from_mesh, extract_planes_and_polygons_from_classified_mesh 25 | from examples.python.util.helper_mesh import create_open_3d_mesh_from_tri_mesh 26 | 27 | 28 | 29 | def convert_to_square_image(pc, invert=False, row=None): 30 | if invert: 31 | row = int(pc.shape[0]) 32 | col = int(pc.shape[1]) 33 | new_pc = pc.reshape((row * col, 3)) 34 | else: 35 | if row is None: 36 | row = int(np.sqrt(pc.shape[0])) 37 | col = row 38 | else: 39 | col = int(pc.shape[0] / row) 40 | shape = (row, col, 3) if pc.ndim > 1 else (row, col) 41 | new_pc = pc.reshape(shape) 42 | return new_pc 43 | 44 | 45 | def create_color(classes): 46 | classes_ = (classes) 47 | colors = cm.tab20(classes_) 48 | colors = colors[:, :, :3] 49 | colors = np.copy(convert_to_square_image(colors, True)) 50 | return colors 51 | 52 | def extract_polygons(opc, classes, config): 53 | pl = Polylidar3D(**config['polylidar']) 54 | ga = GaussianAccumulatorS2Beta(level=config['fastga']['level']) 55 | ico = IcoCharts(level=config['fastga']['level']) 56 | # 1. Create mesh 57 | tri_mesh = create_meshes_cuda( 58 | opc, **config['mesh']['filter']) 59 | 60 | mesh_o3d = create_open_3d_mesh_from_tri_mesh(tri_mesh) 61 | classes = classes.reshape((np.asarray(mesh_o3d.vertices).shape[0], 1)) 62 | classes_mat = MatrixUInt8(classes) 63 | tri_mesh.set_vertex_classes(classes_mat, True) 64 | # alg_timings.update(timings) 65 | # 2. Get dominant plane normals 66 | avg_peaks, _, _, _, alg_timings = extract_all_dominant_plane_normals( 67 | tri_mesh, ga_=ga, ico_chart_=ico, **config['fastga']) 68 | 69 | # alg_timings.update(timings) 70 | # 3. Extract Planes and Polygons 71 | planes, obstacles, timings = extract_planes_and_polygons_from_classified_mesh(tri_mesh, avg_peaks, pl_=pl, 72 | filter_polygons=True, optimized=True, 73 | postprocess=config['polygon']['postprocess']) 74 | 75 | return mesh_o3d, planes, obstacles, timings 76 | 77 | def handle_shapes(planes, obstacles, line_radius=0.15): 78 | all_polys = [] 79 | 80 | for plane, _ in planes: 81 | points = np.array(plane.exterior) 82 | line_mesh = LineMesh(points, colors=[0,1,0], radius=line_radius) 83 | all_polys.append(line_mesh) 84 | 85 | for plane, _ in obstacles: 86 | points = np.array(plane.exterior) 87 | line_mesh = LineMesh(points, colors=[1, 0, 0], radius=line_radius) 88 | all_polys.append(line_mesh) 89 | 90 | all_meshes = [poly.cylinder_segments for poly in all_polys] 91 | all_meshes = functools.reduce(operator.iconcat, all_meshes, []) 92 | 93 | return all_polys, all_meshes 94 | 95 | def main(): 96 | # Load yaml file 97 | config = None 98 | with open('./examples/python/for_landing/PolylidarParams.yaml') as file: 99 | try: 100 | config = yaml.safe_load(file) 101 | except yaml.YAMLError as exc: 102 | print("Error parsing yaml") 103 | 104 | example = Path("./fixtures/unreal/example1") 105 | pc_lidar = np.load(example / "140-0-0.npy") 106 | 107 | pc_image = convert_to_square_image(pc_lidar[:, :3], row=64) 108 | classes = convert_to_square_image(pc_lidar[:, 3], row=64).astype(np.uint8) 109 | colors = create_color(classes) 110 | mask = classes == 4 111 | classes[~mask] = 0 112 | classes[mask] = 1 113 | 114 | 115 | pcd = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(pc_lidar[:, :3])) 116 | pcd.colors = o3d.utility.Vector3dVector(colors) 117 | o3d.visualization.draw_geometries([pcd]) 118 | 119 | 120 | mesh_o3d, planes, obstacles, time = extract_polygons(pc_image, classes, config) 121 | 122 | 123 | all_polys, all_meshes = handle_shapes(planes, obstacles) 124 | all_meshes = [mesh_o3d, *all_meshes] 125 | o3d.visualization.draw_geometries([mesh_o3d, pcd, *all_meshes]) 126 | 127 | 128 | 129 | 130 | if __name__ == "__main__": 131 | main() 132 | -------------------------------------------------------------------------------- /examples/python/for_landing/basic_rooftop_classification_algorithm.py: -------------------------------------------------------------------------------- 1 | """Example Multipolygon Extraction 2 | """ 3 | import time 4 | import math 5 | 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | from mpl_toolkits.mplot3d import Axes3D 9 | 10 | 11 | from polylidar import MatrixDouble, Polylidar3D 12 | from polylidar.polylidarutil import (plot_polygons_3d, generate_3d_plane, set_axes_equal, plot_planes_3d, 13 | scale_points, rotation_matrix, apply_rotation) 14 | 15 | 16 | def extract_polygons(opc, classes, config): 17 | pl = Polylidar3D(**config['polylidar']) 18 | ga = GaussianAccumulatorS2Beta(level=config['fastga']['level']) 19 | ico = IcoCharts(level=config['fastga']['level']) 20 | # 1. Create mesh 21 | tri_mesh = create_meshes_cuda( 22 | opc, **config['mesh']['filter']) 23 | 24 | mesh_o3d = create_open_3d_mesh_from_tri_mesh(tri_mesh) 25 | classes = classes.reshape((np.asarray(mesh_o3d.vertices).shape[0], 1)) 26 | classes_mat = MatrixUInt8(classes) 27 | tri_mesh.set_vertex_classes(classes_mat, True) 28 | # alg_timings.update(timings) 29 | # 2. Get dominant plane normals 30 | avg_peaks, _, _, _, alg_timings = extract_all_dominant_plane_normals( 31 | tri_mesh, ga_=ga, ico_chart_=ico, **config['fastga']) 32 | 33 | # alg_timings.update(timings) 34 | # 3. Extract Planes and Polygons 35 | planes, obstacles, timings = extract_planes_and_polygons_from_classified_mesh(tri_mesh, avg_peaks, pl_=pl, 36 | filter_polygons=True, optimized=True, 37 | postprocess=config['polygon']['postprocess']) 38 | 39 | return mesh_o3d, planes, obstacles, timings 40 | 41 | def main(): 42 | np.random.seed(1) 43 | # generate random plane with hole 44 | plane = generate_3d_plane(bounds_x=[0, 10, 0.5], bounds_y=[0, 10, 0.5], holes=[ 45 | [[3, 5], [3, 5]]], height_noise=0.02, planar_noise=0.02) 46 | # Generate top of box (causing the hole that we see) 47 | box_top = generate_3d_plane(bounds_x=[3, 5, 0.2], bounds_y=[3, 5, 0.2], holes=[ 48 | ], height_noise=0.02, height=2, planar_noise=0.02) 49 | # Generate side of box (causing the hole that we see) 50 | box_side = generate_3d_plane(bounds_x=[0, 2, 0.2], bounds_y=[ 51 | 0, 2, 0.2], holes=[], height_noise=0.02, planar_noise=0.02) 52 | rm = rotation_matrix([0, 1, 0], -math.pi / 2.0) 53 | box_side = apply_rotation(rm, box_side) + [5, 3, 0] 54 | # All points joined together 55 | points = np.concatenate((plane, box_side, box_top)) 56 | 57 | points_mat = MatrixDouble(points) 58 | polylidar_kwargs = dict(alpha=0.0, lmax=1.0, min_triangles=20, z_thresh=0.1, norm_thresh_min=0.94) 59 | polylidar = Polylidar3D(**polylidar_kwargs) 60 | 61 | elev = 15.0 62 | azim = -35 63 | 64 | # Show Point Cloud 65 | print("Should see point raw point cloud") 66 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 67 | subplot_kw=dict(projection='3d')) 68 | # plot points 69 | ax.scatter(*scale_points(points), s=20.0, c=points[:, 2], cmap=plt.cm.plasma) 70 | set_axes_equal(ax) 71 | ax.view_init(elev=elev, azim=azim) 72 | fig.savefig("assets/scratch/Basic25DAlgorithm_pointcloud.pdf", bbox_inches='tight') 73 | fig.savefig("assets/scratch/Basic25DAlgorithm_pointcloud.png", bbox_inches='tight', pad_inches=-0.8) 74 | plt.show() 75 | 76 | # Extracts planes and polygons, time 77 | t1 = time.time() 78 | mesh, planes, polygons = polylidar.extract_planes_and_polygons(points_mat) 79 | t2 = time.time() 80 | print("Polylidar Took {:.2f} milliseconds".format((t2 - t1) * 1000)) 81 | 82 | triangles = np.asarray(mesh.triangles) 83 | all_planes = [np.arange(triangles.shape[0])] 84 | 85 | # Show Triangulation 86 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 87 | subplot_kw=dict(projection='3d')) 88 | 89 | plot_planes_3d(points, triangles, all_planes, ax, alpha=0.0, z_value=-4.0) 90 | plot_planes_3d(points, triangles, all_planes, ax, alpha=0.5) 91 | # plot points 92 | ax.scatter(*scale_points(points), s=0.1, c='k') 93 | set_axes_equal(ax, ignore_z=True) 94 | ax.set_zlim3d([-4, 6]) 95 | ax.view_init(elev=elev, azim=azim) 96 | print("Should see triangulation point cloud") 97 | fig.savefig("assets/scratch/Basic25DAlgorithm_mesh.pdf", bbox_inches='tight') 98 | fig.savefig("assets/scratch/Basic25DAlgorithm_mesh.png", bbox_inches='tight', pad_inches=-0.8) 99 | plt.show() 100 | 101 | print("") 102 | print("Should see two planes extracted, please rotate.") 103 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 104 | subplot_kw=dict(projection='3d')) 105 | # plot all triangles 106 | plot_polygons_3d(points, polygons, ax) 107 | plot_planes_3d(points, triangles, planes, ax) 108 | # plot points 109 | ax.scatter(*scale_points(points), c='k', s=0.1) 110 | set_axes_equal(ax) 111 | ax.view_init(elev=elev, azim=azim) 112 | fig.savefig("assets/scratch/Basic25DAlgorithm_polygons.pdf", bbox_inches='tight') 113 | fig.savefig("assets/scratch/Basic25DAlgorithm_polygons.png", bbox_inches='tight', pad_inches=-0.8) 114 | plt.show() 115 | print("") 116 | 117 | 118 | if __name__ == "__main__": 119 | main() 120 | -------------------------------------------------------------------------------- /examples/python/for_landing/o3d_slow_down.py: -------------------------------------------------------------------------------- 1 | """Demo of Open3D 0.10.0 Slowdown 2 | Please modify DIRECTORY to point to the folder of meshes attached to this issue reply 3 | """ 4 | import os 5 | import open3d as o3d 6 | DIRECTORY = 'o3d_slow_down' 7 | def main(): 8 | all_meshes = [] 9 | all_files = sorted(list(os.listdir(DIRECTORY))) 10 | for filename in all_files: 11 | print(filename) 12 | mesh = o3d.io.read_triangle_mesh(os.path.join(DIRECTORY, filename)) 13 | all_meshes.append(mesh) 14 | o3d.visualization.draw_geometries(all_meshes) 15 | 16 | if __name__ == "__main__": 17 | main() -------------------------------------------------------------------------------- /examples/python/for_paper/basic25d_algorithm_explained.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | from mpl_toolkits.mplot3d import Axes3D 7 | import matplotlib 8 | 9 | matplotlib.rc('xtick', labelsize=14) 10 | matplotlib.rc('ytick', labelsize=14) 11 | # matplotlib.rc('ztick', labelsize=20) 12 | matplotlib.rc('axes', labelsize=18) 13 | 14 | 15 | from polylidar import Delaunator, MatrixDouble, Polylidar3D 16 | from polylidar.polylidarutil import (plot_polygons_3d, generate_3d_plane, set_axes_equal, plot_planes_3d, 17 | scale_points, rotation_matrix, apply_rotation) 18 | 19 | 20 | 21 | def set_labels(ax): 22 | ax.set_xlabel('X') 23 | ax.set_ylabel('Y') 24 | ax.set_zlabel('Z') 25 | 26 | 27 | def main(): 28 | np.random.seed(1) 29 | # generate random plane with hole 30 | plane = generate_3d_plane(bounds_x=[0, 10, 0.5], bounds_y=[0, 10, 0.5], holes=[ 31 | [[3, 6], [3, 6]]], height_noise=0.04, planar_noise=0.04) 32 | 33 | large_wall = generate_3d_plane(bounds_x=[0.5, 4, 1.0], bounds_y=[0, 9.5, 0.5], holes=[], height_noise=0.04, planar_noise=0.1) 34 | # Generate top of box (causing the hole that we see) 35 | box_top = generate_3d_plane(bounds_x=[3, 6, 0.5], bounds_y=[3, 6, 0.5], holes=[ 36 | ], height_noise=0.02, height=2, planar_noise=0.03) 37 | # Generate side of box (causing the hole that we see) 38 | box_side = generate_3d_plane(bounds_x=[.25, 2, 0.5], bounds_y=[ 39 | 0, 3, 0.5], holes=[], height_noise=0.02, planar_noise=0.02) 40 | rm = rotation_matrix([0,1,0], -math.pi/2.0) 41 | box_side = apply_rotation(rm, box_side) + [6, 3, 0] 42 | 43 | large_wall = apply_rotation(rm , large_wall) + [10, 0, -3.5] 44 | # box_side = r.apply(box_side) + [5, 3, 0] 45 | # All points joined together 46 | points = np.concatenate((plane, box_side, box_top, large_wall)) 47 | 48 | points_mat = MatrixDouble(points) 49 | polylidar_kwargs = dict(alpha=0.0, lmax=1.0, min_triangles=10, z_thresh=0.15, norm_thresh_min=0.95) 50 | polylidar = Polylidar3D(**polylidar_kwargs) 51 | 52 | elev = 15.0 53 | azim = -35 54 | 55 | # Show Point Cloud 56 | print("Should see point raw point cloud") 57 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 58 | subplot_kw=dict(projection='3d')) 59 | # plot points 60 | ax.scatter(*scale_points(points), s=20.0, c=points[:, 2], cmap=plt.cm.plasma) 61 | set_axes_equal(ax, ignore_z=True) 62 | ax.set_zlim3d([-4, 6]) 63 | ax.view_init(elev=elev, azim=azim) 64 | set_labels(ax) 65 | fig.savefig("assets/scratch/Basic25DAlgorithm_pointcloud.pdf", bbox_inches='tight') 66 | fig.savefig("assets/scratch/Basic25DAlgorithm_pointcloud.png", bbox_inches='tight', pad_inches=-0.8) 67 | plt.show() 68 | 69 | 70 | # Extracts planes and polygons, time 71 | t1 = time.time() 72 | mesh, planes, polygons = polylidar.extract_planes_and_polygons(points_mat) 73 | t2 = time.time() 74 | print("Polylidar Took {:.2f} milliseconds".format((t2 - t1) * 1000)) 75 | 76 | triangles = np.asarray(mesh.triangles) 77 | all_planes = [np.arange(triangles.shape[0])] 78 | 79 | # Show Triangulation 80 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 81 | subplot_kw=dict(projection='3d')) 82 | 83 | plot_planes_3d(points, triangles, all_planes, ax, alpha=0.0, z_value=-4.0) 84 | plot_planes_3d(points, triangles, all_planes, ax, alpha=0.5) 85 | # plot points 86 | ax.scatter(*scale_points(points), s=0.1, c='k') 87 | set_axes_equal(ax, ignore_z=True) 88 | ax.set_zlim3d([-4, 6]) 89 | ax.view_init(elev=elev, azim=azim) 90 | set_labels(ax) 91 | print("Should see triangulation point cloud") 92 | fig.savefig("assets/scratch/Basic25DAlgorithm_mesh.pdf", bbox_inches='tight') 93 | fig.savefig("assets/scratch/Basic25DAlgorithm_mesh.png", bbox_inches='tight', pad_inches=-0.8) 94 | plt.show() 95 | 96 | 97 | 98 | print("") 99 | print("Should see two planes extracted, please rotate.") 100 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 101 | subplot_kw=dict(projection='3d')) 102 | # plot all triangles 103 | plot_polygons_3d(points, polygons, ax) 104 | plot_planes_3d(points, triangles, planes, ax) 105 | # plot points 106 | ax.scatter(*scale_points(points), c='k', s=0.1) 107 | set_axes_equal(ax, ignore_z=True) 108 | ax.set_zlim3d([-4, 6]) 109 | ax.view_init(elev=elev, azim=azim) 110 | set_labels(ax) 111 | fig.savefig("assets/scratch/Basic25DAlgorithm_polygons.pdf", bbox_inches='tight') 112 | fig.savefig("assets/scratch/Basic25DAlgorithm_polygons.png", bbox_inches='tight', pad_inches=-0.8) 113 | plt.show() 114 | print("") 115 | 116 | if __name__ == "__main__": 117 | main() 118 | -------------------------------------------------------------------------------- /examples/python/for_paper/bench_mesh_report.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pandas as pd 3 | import seaborn as sns 4 | import matplotlib.pyplot as plt 5 | 6 | sns.set(font_scale=1.15) 7 | sns.set_style('white') 8 | sns.set_style("ticks") 9 | 10 | 11 | 12 | def plot_df(df, name='basement'): 13 | fig = plt.figure(figsize=(6,3.5)) 14 | ax = sns.lineplot(x="threads", y="Speedup", hue="normals", palette='deep', data=df, legend=False) 15 | ax2 = ax.twinx() 16 | sns.lineplot(x="threads", y="Execution Time (ms)", ax=ax2, hue='normals',palette='muted', data=df, legend=False) 17 | for line in ax2.lines: 18 | line.set_linestyle('--') 19 | 20 | current_palette = sns.color_palette() 21 | from matplotlib.lines import Line2D 22 | custom_lines = [Line2D([0], [0], color=current_palette[i], lw=1) for i in range(4)] 23 | custom_lines_2 = [Line2D([0], [0], color='k', lw=1), 24 | Line2D([0], [0], color='k', lw=1, dashes=[2,6])] 25 | 26 | ax.legend(custom_lines, ['1 DP', '2 DP', '3 DP', '4 DP'], loc='upper left', bbox_to_anchor=(0.1, 1,)) 27 | ax2.legend(custom_lines_2, ['Speedup', 'Execution Time'], loc='upper left', bbox_to_anchor=(0.35, 1,)) 28 | # ax.axis('equal') 29 | # ax.legend(custom_lines, ['1 DP', '2 DP', '3 DP', '4 DP'], loc='upper left', bbox_to_anchor=(0.4, 0.4,)) 30 | # ax2.legend(custom_lines_2, ['Speedup', 'Execution Time'], loc='upper left', bbox_to_anchor=(0.6, 0.4,)) 31 | fig.savefig(f'assets/scratch/{name}_speedup.pdf', bbox_inches='tight') 32 | plt.tight_layout() 33 | plt.show() 34 | 35 | 36 | def main(): 37 | with open('bench/records/bench_mesh_paper.json') as json_file: 38 | data = json.load(json_file) 39 | benchmarks = data['benchmarks'] 40 | 41 | benchmarks_filtered = [] 42 | 43 | for i, bench in enumerate(benchmarks): 44 | name = bench['name'] 45 | run_name = bench['run_name'] 46 | aggregate_name = bench['aggregate_name'] 47 | if i % 3 == 0: 48 | names = name.split('/') 49 | bench['mesh'] = names[0] 50 | bench['normals'] = int(names[2]) 51 | bench['threads'] = int(names[3]) 52 | bench['time_mean'] = bench['real_time'] if bench['time_unit'] == 'ms' else bench['real_time'] / 1000 53 | bench_median = benchmarks[i+1] 54 | bench['time_median'] = bench_median['real_time'] if bench_median['time_unit'] == 'ms' else bench_median['real_time'] / 1000 55 | bench_std = benchmarks[i+2] 56 | bench['time_std'] = bench_std['real_time'] if bench_std['time_unit'] == 'ms' else bench_std['real_time'] / 1000 57 | benchmarks_filtered.append(bench) 58 | # calculate speedup 59 | current_speed = None 60 | for i, bench in enumerate(benchmarks_filtered): 61 | # reset current speed every 8 iterations (8 threads) 62 | if i % 8 == 0: 63 | current_speed = bench['time_mean'] 64 | bench['speedup'] = current_speed / bench['time_mean'] 65 | 66 | df = pd.DataFrame.from_records(benchmarks_filtered) 67 | df_reduced = df[['mesh', 'normals', 'threads', 'speedup', 'time_mean', 'time_median', 'time_std']] 68 | df_reduced = df_reduced.rename(columns={'time_mean': "Execution Time (ms)", "speedup": "Speedup"}) 69 | 70 | df_sparse = df_reduced[df_reduced['mesh'] == 'SparseMeshPaper'] 71 | df_dense = df_reduced[df_reduced['mesh'] == 'DenseMeshPaper'] 72 | 73 | plot_df(df_sparse, 'basement') 74 | plot_df(df_dense, 'mainfloor') 75 | 76 | if __name__ == "__main__": 77 | main() -------------------------------------------------------------------------------- /examples/python/for_paper/california_points.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from tests.python.helpers.utils import load_csv, load_npy 3 | 4 | 5 | def plot_points(points): 6 | 7 | fig, ax = plt.subplots(nrows=1, ncols=1) 8 | ax.scatter(points[:,0], points[:, 1], s=1) 9 | plt.axis('off') 10 | ax.axis('equal') 11 | 12 | plt.show() 13 | 14 | 15 | def main(): 16 | points_1 = load_csv('caholes_8000.csv', delimeter=' ') 17 | plot_points(points_1) 18 | 19 | points_1 = load_csv('ca_8000.csv', delimeter=' ') 20 | plot_points(points_1) 21 | 22 | 23 | if __name__ == "__main__": 24 | main() -------------------------------------------------------------------------------- /examples/python/for_paper/generate_logo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pickle 3 | import numpy as np 4 | from polylidar import Delaunator, MatrixDouble, Polylidar3D 5 | from polylidar.polylidarutil import (generate_test_points, plot_points, plot_triangles, get_estimated_lmax, 6 | plot_triangle_meshes, get_triangles_from_list, get_colored_planar_segments, plot_polygons) 7 | import matplotlib.pyplot as plt 8 | 9 | np.random.seed(1) 10 | 11 | 12 | def get_np_buffer_ptr(a): 13 | pointer, read_only_flag = a.__array_interface__['data'] 14 | return pointer 15 | 16 | 17 | def stitch_letters(letters_map, letters=['P', 'L']): 18 | # letters = ['P', 'O', 'L', 'Y', 'L', 'I', 'D', 'A', 'R'] 19 | x_offset = np.array([0.0, 0.0]) 20 | x_spacing = np.array([-30.0, 0.0]) 21 | all_points = [] 22 | for letter in letters: 23 | letter_points = np.copy(letters_map[letter]) 24 | letter_points = letter_points + x_offset + x_spacing 25 | x_offset[0] = letter_points.max(axis=0)[0] 26 | all_points.append(letter_points) 27 | 28 | all_points = np.concatenate(all_points, axis=0) 29 | 30 | return all_points 31 | 32 | def read_alphabet(file_name='fixtures/pointsets/alphabet_2000.pkl'): 33 | with open(file_name, 'rb') as file: 34 | data = pickle.load(file) 35 | letters = ['P', 'O', 'L', 'Y', 'I', 'D', 'A', 'R'] 36 | letter_map = dict() 37 | for letter in letters: 38 | letter_map[letter] = [item for item in data if item['poly_param']['name'] == letter][0]['points'] 39 | 40 | return letter_map 41 | 42 | 43 | def main(): 44 | 45 | letter_map = read_alphabet() 46 | # letters = ['P', 'O', 'L', 'Y', 'L', 'I', 'D', 'A', 'R'] 47 | points = stitch_letters(letter_map) 48 | 49 | # plt.scatter(points[:, 0], points[:, 1], s=0.2) 50 | # plt.show() 51 | print(int(points.shape[0] / 2)) 52 | idx = np.random.randint(0, points.shape[0], size=int(points.shape[0] / 2)) 53 | points = np.ascontiguousarray(points[idx, :]) 54 | lmax = 10.0 55 | polylidar_kwargs = dict(alpha=0.0, lmax=lmax, min_triangles=5) 56 | # print(polylidar_kwargs) 57 | # Convert Points and make Polylidar 58 | points_mat = MatrixDouble(points) 59 | polylidar = Polylidar3D(**polylidar_kwargs) 60 | # Extracts planes and polygons, time 61 | t1 = time.perf_counter() 62 | mesh, planes, polygons = polylidar.extract_planes_and_polygons(points_mat) 63 | t2 = time.perf_counter() 64 | triangles = np.asarray(mesh.triangles) 65 | # print(triangles, triangles.shape) 66 | triangles = triangles.flatten() 67 | # print(triangles, triangles.shape) 68 | planes_np = planes 69 | # print(planes_np) 70 | print("Took {:.2f} milliseconds".format((t2 - t1) * 1000)) 71 | 72 | # Plot Data 73 | if points.shape[0] < 100000: 74 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1) 75 | ax.set_xticks([]) 76 | ax.set_yticks([]) 77 | plt.axis('equal') 78 | plt.axis('off') 79 | # plot points 80 | plot_points(points, ax) 81 | fig.savefig('assets/scratch/pl_logo_points.png', bbox_inches='tight', transparent=True) 82 | # plot polygons 83 | plot_polygons(polygons, points, ax, linewidth=6.0) 84 | fig.savefig('assets/scratch/pl_logo_poly.png', bbox_inches='tight', transparent=True) 85 | # plot all triangles 86 | plot_triangles(get_triangles_from_list(triangles, points), ax) 87 | # plot mesh triangles 88 | triangle_meshes = get_colored_planar_segments(planes_np, triangles, points) 89 | plot_triangle_meshes(triangle_meshes, ax) 90 | # plot polygons 91 | plot_polygons(polygons, points, ax, linewidth=4.0) 92 | 93 | plt.subplots_adjust(wspace=0.185, hspace=0.185,left=0.110,top=0.535,right=0.750,bottom=0.110) 94 | fig.savefig('assets/scratch/pl_logo.png', bbox_inches='tight', transparent=True) 95 | plt.show() 96 | 97 | 98 | if __name__ == "__main__": 99 | main() 100 | -------------------------------------------------------------------------------- /examples/python/for_paper/manifold_meshes.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | from mpl_toolkits.mplot3d import Axes3D 7 | import matplotlib 8 | 9 | matplotlib.rc('xtick', labelsize=14) 10 | matplotlib.rc('ytick', labelsize=14) 11 | # matplotlib.rc('ztick', labelsize=20) 12 | matplotlib.rc('axes', labelsize=18) 13 | 14 | 15 | from polylidar import Delaunator, MatrixDouble, Polylidar3D 16 | from polylidar.polylidarutil import (plot_polygons_3d, generate_3d_plane, set_axes_equal, plot_planes_3d, 17 | scale_points, rotation_matrix, apply_rotation) 18 | 19 | 20 | 21 | def set_labels(ax): 22 | ax.set_xlabel('X') 23 | ax.set_ylabel('Y') 24 | ax.set_zlabel('Z') 25 | 26 | def triangulate_and_combine(points, points_bad, triangles_bad): 27 | points_mat = MatrixDouble(points) 28 | mesh = Delaunator(points_mat) 29 | mesh.triangulate() 30 | 31 | triangles = np.asarray(mesh.triangles) 32 | points_new = np.concatenate([points, points_bad], axis=0) 33 | triangles_new = np.concatenate([triangles, triangles_bad], axis=0) 34 | all_planes = [np.arange(triangles_new.shape[0])] 35 | 36 | return mesh, points_new, triangles_new, all_planes 37 | 38 | def main(): 39 | np.random.seed(1) 40 | # generate random plane with hole 41 | 42 | z_lim = [-4, 6] 43 | elev = 10.0 44 | azim = -35 45 | 46 | points = generate_3d_plane(bounds_x=[2.0, 6.0, 1.0], bounds_y=[2.0, 6.0, 0.5], holes=[], planar_noise=0.0, height_noise=0.01) 47 | 48 | points_bad = np.array([ 49 | [3.0, 3.5, 0.0], 50 | [3.5, 3.5, 1.0], 51 | [4.0, 3.5, 0.0] 52 | ]) 53 | end_point_idx = points.shape[0] 54 | triangles_bad = np.array([ 55 | [end_point_idx, end_point_idx + 1, end_point_idx + 2] 56 | ]) 57 | 58 | mesh, points_new, triangles_new, all_planes = triangulate_and_combine(points, points_bad, triangles_bad) 59 | 60 | # Show Triangulation 61 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 62 | subplot_kw=dict(projection='3d')) 63 | 64 | plot_planes_3d(points_new, triangles_new, all_planes, ax, alpha=0.5) 65 | set_axes_equal(ax, ignore_z=True) 66 | ax.set_zlim3d(z_lim) 67 | ax.view_init(elev=elev, azim=azim) 68 | plt.show() 69 | 70 | 71 | points = generate_3d_plane(bounds_x=[2.0, 6.0, 1.0], bounds_y=[2.0, 6.0, 0.5], holes=[], planar_noise=0.0, height_noise=0.01) 72 | points[18, 2] = points[18, 2] + 0.30 73 | points_bad = np.array([ 74 | [3.0, 3.5, 0.0], 75 | [3.5, 4.0, 0.20], 76 | [4.0, 3.5, 0.0] 77 | ]) 78 | end_point_idx = points.shape[0] 79 | triangles_bad = np.array([ 80 | [end_point_idx, end_point_idx + 1, end_point_idx + 2] 81 | ]) 82 | 83 | mesh, points_new, triangles_new, all_planes = triangulate_and_combine(points, points_bad, triangles_bad) 84 | # all_planes.append([13, 14]) 85 | # Show Triangulation 86 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, 87 | subplot_kw=dict(projection='3d')) 88 | 89 | plot_planes_3d(points_new, triangles_new, all_planes, ax, alpha=0.5) 90 | # plot_planes_3d(points_new, triangles_new, [[triangles_new.shape[0]-1]], ax, alpha=1.0, color=(1,0,0)) 91 | 92 | ax.plot_trisurf(*scale_points(points_new, z_value=None, z_scale=1.0),triangles=triangles_new[[triangles_new.shape[0]-1]], color=(1,0,0,1.0), edgecolor=(0,0,0,0.3), linewidth=0.5, antialiased=True, zorder=1.5) 93 | ax.plot_trisurf(*scale_points(points_new, z_value=None, z_scale=1.0),triangles=triangles_new[[13,14]], color=(0,1,0,0.5), edgecolor=(0,0,0,0.3), linewidth=0.5, antialiased=False, zorder=0.5) 94 | # plot_planes_3d(points_new, triangles_new, [[13,14]], ax, alpha=0.9, color=(0,1,0)) 95 | set_axes_equal(ax, ignore_z=True) 96 | ax.set_zlim3d(z_lim) 97 | ax.view_init(elev=elev, azim=azim) 98 | plt.show() 99 | 100 | 101 | 102 | 103 | 104 | 105 | if __name__ == "__main__": 106 | main() 107 | -------------------------------------------------------------------------------- /examples/python/robust.py: -------------------------------------------------------------------------------- 1 | """Demonstrates Polylidar3D using Robust Geometric Predicates with Delaunator 2 | You need to have built Polylidar3D with this enabled to see correct polygon generation 3 | """ 4 | import time 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | from tests.python.helpers.utils import load_csv 8 | from polylidar import Polylidar3D, MatrixDouble 9 | from polylidar.polylidarutil import (plot_points, plot_triangles, 10 | plot_triangle_meshes, get_triangles_from_list, get_colored_planar_segments, plot_polygons) 11 | 12 | # Load point set that for which delaunator generates invalid convex hulls when using non-robust predicates 13 | # Convex hull should be malformed if polylidar not built with robust predicates 14 | def main(): 15 | points = load_csv('robust_1.csv') 16 | polylidar_kwargs = dict(alpha=0.0, lmax=1000.0, min_triangles=1) 17 | 18 | points_mat = MatrixDouble(points) 19 | polylidar = Polylidar3D(**polylidar_kwargs) 20 | 21 | # Extracts planes and polygons, time 22 | t1 = time.time() 23 | # pick large lmax to get the convex hull, no triangles filtered 24 | mesh, planes, polygons = polylidar.extract_planes_and_polygons(points_mat) 25 | t2 = time.time() 26 | print("Took {:.2f} milliseconds".format((t2 - t1) * 1000)) 27 | print("If Robust Geometric Predicates is NOT activated, you should see a malformed convex hull") 28 | print("See README.md to activate if desired. ~20% performance penalty when active.") 29 | print("") 30 | # Convert to numpy format, no copy with np.asarray() 31 | triangles = np.asarray(mesh.triangles) 32 | 33 | fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1) 34 | # plot points 35 | ax.scatter(points[:, 0], points[:, 1], c='k') 36 | # plot all triangles 37 | plot_triangles(get_triangles_from_list(triangles, points), ax) 38 | # plot seperated planar triangular segments 39 | triangle_meshes = get_colored_planar_segments(planes, triangles, points) 40 | plot_triangle_meshes(triangle_meshes, ax) 41 | # plot polygons 42 | plot_polygons(polygons, points, ax) 43 | plt.axis('equal') 44 | plt.show() 45 | 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /examples/python/util/mesh_util.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | import sys 4 | from os import path, listdir 5 | from os.path import exists, isfile, join, splitext 6 | import re 7 | import logging 8 | 9 | import numpy as np 10 | from polylidar.polylidarutil import COLOR_PALETTE 11 | from polylidar.polylidarutil.line_mesh import o3d_major_version 12 | import open3d as o3d 13 | from scipy.spatial.transform import Rotation as R 14 | 15 | DIR_NAME = path.dirname(__file__) 16 | FIXTURES_DIR = path.join(DIR_NAME, '../../..', 'fixtures') 17 | MESHES_DIR = path.join(FIXTURES_DIR, 'meshes') 18 | 19 | DENSE_MESH = path.join(MESHES_DIR, 'dense_first_floor_map_smoothed.ply') 20 | SPARSE_MESH = path.join(MESHES_DIR, 'sparse_basement.ply') 21 | BASEMENT_CHAIR = path.join(MESHES_DIR, 'basement_chair_5cm.ply') 22 | 23 | ALL_MESHES = [DENSE_MESH, SPARSE_MESH, BASEMENT_CHAIR] 24 | # ALL_MESHES_ROTATIONS = [R.from_rotvec(-np.pi / 2 * np.array([1, 0, 0])), 25 | # R.from_rotvec(-np.pi / 2 * np.array([1, 0, 0]))] 26 | 27 | ALL_MESHES_ROTATIONS = [None, None, None] 28 | 29 | 30 | def get_mesh_data_iterator(): 31 | for i, (mesh_fpath, r) in enumerate(zip(ALL_MESHES, ALL_MESHES_ROTATIONS)): 32 | example_mesh = o3d.io.read_triangle_mesh(str(mesh_fpath)) 33 | if r is not None: 34 | center = [0, 0, 0] if o3d_major_version > 9 else True 35 | example_mesh = example_mesh.rotate(r.as_matrix(), center=center) 36 | example_mesh_filtered = example_mesh 37 | example_mesh_filtered.compute_triangle_normals() 38 | yield example_mesh_filtered 39 | 40 | 41 | def main(): 42 | for i, mesh in enumerate(get_mesh_data_iterator()): 43 | if i < 1: 44 | continue 45 | colors = np.asarray(mesh.vertex_colors) 46 | colors2 = np.column_stack((colors[:, 2], colors[:, 1], colors[:, 0])) 47 | mesh.vertex_colors = o3d.utility.Vector3dVector(colors2) 48 | o3d.io.write_triangle_mesh('test.ply', mesh) 49 | 50 | if __name__ == "__main__": 51 | main() 52 | -------------------------------------------------------------------------------- /fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # But not these files... 5 | !.gitignore -------------------------------------------------------------------------------- /include/Polylidar/Core.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYLIDAR_CORE 2 | #define POLYLIDAR_CORE 3 | #include 4 | 5 | #include "Polylidar/Types.hpp" 6 | #include "Polylidar/Utility.hpp" 7 | #include "Polylidar/Mesh/MeshHelper.hpp" 8 | 9 | namespace Polylidar { 10 | 11 | namespace Core { 12 | 13 | 14 | 15 | void ExtractMeshSet(MeshHelper::HalfEdgeTriangulation& mesh, std::vector& tri_set, size_t seed_idx, 16 | std::vector& candidates, const PlaneData &plane_data, const double &z_thresh); 17 | void ConstructPointHash(VUI& plane, MeshHelper::HalfEdgeTriangulation& mesh, PointHash& point_hash, EdgeSet& edge_hash, 18 | ExtremePoint& xPoint, PlaneData& plane_data); 19 | 20 | Polygons ExtractConcaveHulls(Planes &planes, MeshHelper::HalfEdgeTriangulation &mesh, PlaneData &plane_data, size_t min_hole_vertices_); 21 | Polygon ExtractConcaveHull(VUI &plane, MeshHelper::HalfEdgeTriangulation &mesh, PlaneData &plane_data, size_t min_hole_vertices_); 22 | 23 | 24 | // Inline Function 25 | 26 | 27 | 28 | inline bool PassPlaneConstraints(std::vector& plane_set, size_t min_triangles) 29 | { 30 | return plane_set.size() >= min_triangles; 31 | } 32 | 33 | inline size_t fast_mod(const size_t i, const size_t c) { return i >= c ? i % c : i; } 34 | 35 | inline size_t nextHalfedge(size_t e) { return fast_mod(e, 3) == 2 ? e - 2 : e + 1; } 36 | 37 | inline std::array GetVector(size_t edge, MeshHelper::HalfEdgeTriangulation& mesh, std::array& rm, 38 | bool& need_rotation, bool flip = false) 39 | { 40 | auto& coords = mesh.vertices; 41 | auto& triangles = mesh.triangles.data; 42 | auto pi = triangles[edge]; 43 | auto piNext = triangles[nextHalfedge(edge)]; 44 | 45 | // Points projected on 2D plane, assumes normal is [0,0,1] 46 | std::array p0 = {coords(pi, 0), coords(pi, 1)}; 47 | std::array p1 = {coords(piNext, 0), coords(piNext, 1)}; 48 | std::array result; 49 | 50 | // Check if points need to be projected onto a different plane 51 | if (need_rotation) 52 | { 53 | // std::cout<< "rotating points for edge: " << edge << std::endl; 54 | // print_matrix(rm); 55 | auto rotated_point = Utility::Math::RotateVector(&coords(pi, 0), rm); 56 | // std::cout<< "p0 before: " << PL_PRINT_ARRAY2(p0) << std::endl; 57 | p0[0] = rotated_point[0]; 58 | p0[1] = rotated_point[1]; 59 | // std::cout<< "p0 after: " << PL_PRINT_ARRAY2(p0) << std::endl; 60 | auto rotated_point_2 = Utility::Math::RotateVector(&coords(piNext, 0), rm); 61 | // std::cout<< "p1 before: " << PL_PRINT_ARRAY2(p1) << std::endl; 62 | p1[0] = rotated_point_2[0]; 63 | p1[1] = rotated_point_2[1]; 64 | // std::cout<< "p1 after: " << PL_PRINT_ARRAY2(p1) << std::endl; 65 | } 66 | 67 | if (flip) 68 | { 69 | result[0] = p0[0] - p1[0]; 70 | result[1] = p0[1] - p1[1]; 71 | } 72 | else 73 | { 74 | result[0] = p1[0] - p0[0]; 75 | result[1] = p1[1] - p0[1]; 76 | } 77 | return result; // RVO 78 | } 79 | 80 | 81 | inline size_t GetHullEdge(const std::array& v1, const std::vector& outgoingEdges, 82 | MeshHelper::HalfEdgeTriangulation& mesh, std::array& rm, bool& need_rotation, 83 | const bool is_ccw = false) 84 | { 85 | std::vector> otherVectors; 86 | std::transform(outgoingEdges.begin(), outgoingEdges.end(), std::back_inserter(otherVectors), 87 | [&mesh, &rm, &need_rotation](size_t edge) -> std::array { 88 | return GetVector(edge, mesh, rm, need_rotation, false); 89 | }); 90 | 91 | std::vector angleDist; 92 | std::transform(otherVectors.begin(), otherVectors.end(), std::back_inserter(angleDist), 93 | [&v1](std::array& outVector) -> double { return Utility::Math::Get360Angle(v1, outVector); }); 94 | 95 | if (is_ccw) 96 | { 97 | auto min_pos = std::distance(angleDist.begin(), std::min_element(angleDist.begin(), angleDist.end())); 98 | return outgoingEdges[min_pos]; 99 | } 100 | else 101 | { 102 | auto max_pos = std::distance(angleDist.begin(), std::max_element(angleDist.begin(), angleDist.end())); 103 | return outgoingEdges[max_pos]; 104 | } 105 | } 106 | 107 | std::vector ConcaveSection(PointHash& pointHash, EdgeSet& edgeHash, MeshHelper::HalfEdgeTriangulation& mesh, 108 | size_t startEdge, size_t stopPoint, PlaneData& plane_data); 109 | 110 | std::vector> ExtractInteriorHoles(PointHash& pointHash, EdgeSet& edgeHash, 111 | MeshHelper::HalfEdgeTriangulation& mesh, PlaneData& plane_data); 112 | 113 | } // namespace Core 114 | 115 | } // namespace Polylidar 116 | 117 | #endif -------------------------------------------------------------------------------- /include/Polylidar/Delaunator/Delaunator.hpp: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2018 Volodymyr Bilonenko and Jeremy Castagno 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #pragma once 24 | 25 | #ifndef DELAUNATOR 26 | #define DELAUNATOR 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #ifdef PL_USE_ROBUST_PREDICATES 38 | #include "predicates.h" 39 | #endif 40 | 41 | #include "Polylidar/Mesh/MeshHelper.hpp" 42 | 43 | namespace Polylidar { 44 | 45 | namespace Delaunator { 46 | 47 | class Delaunator : public Polylidar::MeshHelper::HalfEdgeTriangulation 48 | { 49 | 50 | public: 51 | std::vector &triangles_ref; 52 | std::vector &halfedges_ref; 53 | std::vector hull_prev; 54 | std::vector hull_next; 55 | std::vector hull_tri; 56 | std::size_t hull_start; 57 | 58 | Delaunator(Matrix &&in_vertices); 59 | Delaunator(const Matrix &in_vertices); 60 | void triangulate(); 61 | 62 | double get_hull_area(); 63 | 64 | private: 65 | std::vector m_hash; 66 | double m_center_x; 67 | double m_center_y; 68 | std::size_t m_hash_size; 69 | std::vector m_edge_stack; 70 | 71 | std::size_t legalize(std::size_t a); 72 | std::size_t hash_key(double x, double y) const; 73 | std::size_t add_triangle(std::size_t i0, std::size_t i1, std::size_t i2, std::size_t a, std::size_t b, 74 | std::size_t c); 75 | void link(std::size_t a, std::size_t b); 76 | }; 77 | 78 | } // namespace delaunator 79 | } // namespace Polylidar 80 | #endif -------------------------------------------------------------------------------- /include/Polylidar/UtilityMath.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLYLIDAR_UTILITY_MATH 2 | #define POLYLIDAR_UTILITY_MATH 3 | 4 | #include "Polylidar/Types.hpp" 5 | 6 | namespace Polylidar { 7 | 8 | namespace Utility { 9 | 10 | namespace Math 11 | 12 | { 13 | 14 | inline double Determinant(const std::array& v1, const std::array& v2) 15 | { 16 | return v1[0] * v2[1] - v1[1] * v2[0]; 17 | } 18 | 19 | inline double DotProduct2(const std::array& v1, const std::array& v2) 20 | { 21 | return v1[0] * v2[0] + v1[1] * v2[1]; 22 | } 23 | 24 | inline void CrossProduct3(const std::array& u, const std::array& v, double* normal) 25 | { 26 | // cross product 27 | normal[0] = u[1] * v[2] - u[2] * v[1]; 28 | normal[1] = u[2] * v[0] - u[0] * v[2]; 29 | normal[2] = u[0] * v[1] - u[1] * v[0]; 30 | } 31 | 32 | inline void Subtract(const std::array& u, const std::array& v, std::array& out) 33 | { 34 | out[0] = u[0] - v[0]; 35 | out[1] = u[1] - v[1]; 36 | out[2] = u[2] - v[2]; 37 | } 38 | 39 | inline void Subtract(const double* u, const double* v, std::array& out) 40 | { 41 | out[0] = u[0] - v[0]; 42 | out[1] = u[1] - v[1]; 43 | out[2] = u[2] - v[2]; 44 | } 45 | 46 | inline void Normalize3(double* normal) 47 | { 48 | auto norm = std::sqrt(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]); 49 | normal[0] /= norm; 50 | normal[1] /= norm; 51 | normal[2] /= norm; 52 | } 53 | 54 | inline double L2Norm(double dx, double dy) { return std::sqrt(dx * dx + dy * dy); } 55 | 56 | inline double L2Norm3D(double dx, double dy, double dz) { return std::sqrt(dx * dx + dy * dy + dz * dz); } 57 | 58 | inline double DotProduct3(const std::array& v1, const std::array& v2) 59 | { 60 | return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; 61 | } 62 | 63 | inline double DotProduct3(const double* v1, const std::array& v2) 64 | { 65 | return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; 66 | } 67 | inline std::array AxisAngleToRotationMatrix(const std::array& axis, const double angle) 68 | { 69 | std::array rm{{1, 0, 0, 0, 1, 0, 0, 0, 1}}; 70 | // TODO Research what is the proper way to handle this 71 | if (std::abs(angle) < PL_EPS_RADIAN) return rm; 72 | auto c = std::cos(angle); 73 | auto s = std::sin(angle); 74 | auto t = 1 - c; 75 | auto& x = axis[0]; 76 | auto& y = axis[1]; 77 | auto& z = axis[2]; 78 | // Creat matrix 79 | rm[0] = t * x * x + c; 80 | rm[1] = t * x * y - z * s; 81 | rm[2] = t * x * z + y * s; 82 | 83 | rm[3] = t * x * y + z * s; 84 | rm[4] = t * y * y + c; 85 | rm[5] = t * y * z - x * s; 86 | 87 | rm[6] = t * x * z - y * s; 88 | rm[7] = t * y * z + x * s; 89 | rm[8] = t * z * z + c; 90 | 91 | return rm; 92 | } 93 | inline std::tuple, double> AxisAngleFromVectors(const std::array& v1, 94 | const std::array& v2) 95 | { 96 | std::array axis; 97 | CrossProduct3(v1, v2, axis.data()); 98 | Normalize3(axis.data()); 99 | double angle = std::acos(DotProduct3(v1, v2)); 100 | 101 | return std::make_tuple(axis, angle); 102 | } 103 | // Rotate Vector 104 | inline std::array RotateVector(const double* v1, const std::array& rm) 105 | { 106 | std::array rv1; 107 | rv1[0] = rm[0] * v1[0] + rm[1] * v1[1] + rm[2] * v1[2]; 108 | rv1[1] = rm[3] * v1[0] + rm[4] * v1[1] + rm[5] * v1[2]; 109 | rv1[2] = rm[6] * v1[0] + rm[7] * v1[1] + rm[8] * v1[2]; 110 | 111 | return rv1; 112 | } 113 | 114 | inline double Get360Angle(const std::array& v1, const std::array& v2) 115 | { 116 | auto dot = DotProduct2(v1, v2); 117 | auto det = Determinant(v1, v2); 118 | auto ang = std::atan2(det, dot); 119 | if (ang < 0) 120 | { 121 | ang += M_PI * 2; 122 | } 123 | return ang; 124 | } 125 | 126 | } // namespace Math 127 | } // namespace Utility 128 | } // namespace Polylidar 129 | #endif -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | 2 | [build-system] 3 | requires = ["setuptools", "wheel", "cmake>=3.14.0"] 4 | 5 | [tool.pytest.ini_options] 6 | minversion = "6.0" 7 | addopts = "-ra -v" 8 | testpaths = [ 9 | "tests/python/generated_test.py" 10 | ] -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | # pytest.ini 2 | [pytest] 3 | minversion = 6.0 4 | addopts = -ra -q 5 | testpaths = 6 | tests/python/generated_test.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.15.0 2 | shapely 3 | matplotlib 4 | -------------------------------------------------------------------------------- /scripts/make_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # exit when any command fails 3 | set -e 4 | 5 | # keep track of the last executed command 6 | trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG 7 | # echo an error message before exiting 8 | trap 'echo "\"${last_command}\" command filed with exit code $?."' EXIT 9 | 10 | python scripts/manage_versions.py --bump patch 11 | pip install . 12 | cd src_docs && python make_docs.py && cd .. 13 | git status 14 | 15 | read -p "Are you sure you want to add all these files? " -n 1 -r 16 | echo # (optional) move to a new line 17 | if [[ $REPLY =~ ^[Yy]$ ]] 18 | then 19 | read -p "Enter commit message: " commmit_message 20 | git add . 21 | git commit -m "$commmit_message" 22 | python scripts/manage_versions.py --tag 23 | echo -e "Dont forget to push:\n \t git push origin dev --tags" 24 | fi -------------------------------------------------------------------------------- /scripts/manage_versions.py: -------------------------------------------------------------------------------- 1 | """ Manage Versions in a python project 2 | This will bump the file holding the current version (src/version.txt) as well as tag releases 3 | """ 4 | import argparse 5 | from pathlib import Path 6 | from git import Repo 7 | import sys 8 | 9 | def parse_args(): 10 | parser = argparse.ArgumentParser("Manage the project version including git tags.") 11 | parser.add_argument('--bump', 12 | default='none', 13 | const='none', 14 | nargs='?', 15 | choices=['major', 'minor', 'patch', 'none'], 16 | help='Bump version of project') 17 | parser.add_argument('--version_file', 18 | default='src/version.txt', 19 | help='Path to version file') 20 | parser.add_argument("--tag", 21 | dest="tag", 22 | action="store_true", 23 | default=False, 24 | help="Create git tag") 25 | args = parser.parse_args() 26 | 27 | assert Path(args.version_file).exists() 28 | 29 | return args 30 | 31 | def bump(bump_type:str='patch', version_file='src/version.txt'): 32 | with open('src/version.txt', 'r') as f: 33 | lines = f.readlines() 34 | version = ([int(line.split(' ')[1].strip()) for line in lines if line]) 35 | 36 | # Increment the version, resetting minor/patches 37 | if bump_type == 'major': 38 | version[0] += 1 39 | version[1] = 0 40 | version[2] = 0 41 | if bump_type == 'minor': 42 | version[1] += 1 43 | version[2] = 0 44 | if bump_type == 'patch': 45 | version[2] += 1 46 | 47 | if bump_type != 'none': 48 | with open('src/version.txt', 'w') as f: 49 | f.write("MAJOR {}\nMINOR {}\nPATCH {}".format(*version)) 50 | else: 51 | pass 52 | # print("Not incrementing version, bump was 'none'.\n") 53 | 54 | string_ints = [str(int_version) for int_version in version] 55 | version_str = ".".join(string_ints) 56 | 57 | return version_str 58 | 59 | def create_tag(version_str, message="Tagged for release"): 60 | version_str = 'v{}'.format(version_str) 61 | repo = Repo('.') 62 | if repo.is_dirty(): 63 | resp:str = input("Repo is dirty do you want to continue? [y/n]: ") 64 | if resp.lower() != 'y': 65 | print("Quiting...") 66 | sys.exit() 67 | hexsha = repo.head.commit.hexsha 68 | print("Creating new tag: {} ; commit: {}".format(version_str, hexsha)) 69 | new_tag = repo.create_tag(version_str, message=message) 70 | return version_str 71 | 72 | def main(): 73 | args = parse_args() 74 | if args.tag: 75 | version_str = bump(bump_type='none', version_file=args.version_file) 76 | new_tag = create_tag(version_str) 77 | print("Don't forget to push the tags with:") 78 | print("\t git push origin {} OR".format(new_tag)) 79 | print("\t git push origin --tags") 80 | else: 81 | version_str = bump(bump_type=args.bump, version_file=args.version_file) 82 | print('Incremented version to {}'.format(version_str)) 83 | 84 | 85 | 86 | 87 | if __name__ == "__main__": 88 | main() -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ############ Start Version Parsing ################## 2 | 3 | file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/version.txt" POLYLIDAR_VERSION_READ) 4 | foreach(ver ${POLYLIDAR_VERSION_READ}) 5 | if (ver MATCHES "(MAJOR|MINOR|PATCH) +([^ ]+)$") 6 | set(POLYLIDAR_VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}" CACHE INTERNAL "") 7 | endif() 8 | endforeach() 9 | string(CONCAT POLYLIDAR_VERSION 10 | "${POLYLIDAR_VERSION_MAJOR}" 11 | ".${POLYLIDAR_VERSION_MINOR}" 12 | ".${POLYLIDAR_VERSION_PATCH}" 13 | ) 14 | 15 | set(PROJECT_VERSION "${POLYLIDAR_VERSION}") 16 | 17 | message("Version is ${PROJECT_VERSION}") 18 | 19 | ############# End Version Parsing ################## 20 | 21 | 22 | add_subdirectory(Polylidar) 23 | 24 | if (PL_BUILD_PYMODULE) 25 | add_subdirectory(Python) 26 | endif () 27 | -------------------------------------------------------------------------------- /src/Polylidar/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | configure_file("PLConfig.hpp.in" "PLConfig.hpp" @ONLY) 3 | 4 | set(POLYLIDAR_SRC "${CMAKE_CURRENT_SOURCE_DIR}") 5 | set(INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include") 6 | set(POLYLIDAR_INCLUDE_DIR "${INCLUDE_DIR}/Polylidar") 7 | set(POLYLIDAR_SOURCE_FILES ${POLYLIDAR_SRC}/Polylidar.cpp ${POLYLIDAR_SRC}/MeshHelper.cpp ${POLYLIDAR_SRC}/MeshFilter.cpp 8 | ${POLYLIDAR_SRC}/Delaunator.cpp ${POLYLIDAR_SRC}/Core.cpp ${POLYLIDAR_SRC}/PLConfig.cpp) 9 | set(POLYLIDAR_HEADER_FILES ${POLYLIDAR_INCLUDE_DIR}/Polylidar.hpp ${POLYLIDAR_INCLUDE_DIR}/Types.hpp 10 | ${POLYLIDAR_INCLUDE_DIR}/Utility.hpp ${POLYLIDAR_INCLUDE_DIR}/Mesh/MeshHelper.hpp 11 | ${POLYLIDAR_INCLUDE_DIR}/Delaunator/Delaunator.hpp ${POLYLIDAR_INCLUDE_DIR}/Core.hpp) 12 | 13 | 14 | set(LIB_TYPE STATIC) 15 | add_library(${PROJECT_NAME} ${LIB_TYPE} ${POLYLIDAR_SOURCE_FILES}) 16 | add_library(Polylidar::PL ALIAS ${PROJECT_NAME}) 17 | 18 | ############# Start Build Options ################### 19 | 20 | # Configure optimization 21 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 22 | set(OPTIMIZATION_FLAGS "-O0 -DDEBUG") 23 | message("-- Configuring debug build") 24 | else() 25 | set(OPTIMIZATION_FLAGS "-O3 -DNDEBUG") 26 | message("-- Configuring release build") 27 | endif() 28 | 29 | # Enable warnings, will set to compile with src/CMakeLists.txt 30 | set(DESIRED_WARNINGS "-Wall -Wextra -Wconversion -Wunreachable-code -Wuninitialized -pedantic-errors -Wold-style-cast -Wno-error=unused-variable -Wshadow -Wfloat-equal -Weffc++") 31 | set(MINIMAL_DESIRED_WARNINGS "-Wall -Wextra -pedantic-errors") 32 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 33 | set(DESIRED_WARNINGS "${DESIRED_WARNINGS} -Wmost") 34 | endif() 35 | separate_arguments(DESIRED_WARNINGS) 36 | separate_arguments(MINIMAL_DESIRED_WARNINGS) 37 | 38 | if (PL_BUILD_WERROR) 39 | set(DESIRED_WARNINGS "${DESIRED_WARNINGS} -Werror") 40 | set(MINIMAL_DESIRED_WARNINGS "${MINIMAL_DESIRED_WARNINGS} -Werror") 41 | endif() 42 | 43 | # Set GLOBAL CMAKE_CXX_FLAGS 44 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPTIMIZATION_FLAGS}") 45 | 46 | ############# End Build Options ##################### 47 | 48 | 49 | # Set Compile Options for ONLY this target. 50 | target_compile_options(${PROJECT_NAME} PRIVATE 51 | $<$,$,$>: 52 | ${DESIRED_WARNINGS}> 53 | $<$: 54 | /W4>) 55 | 56 | # To include the PLConfig.hpp file 57 | target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 58 | 59 | # Link with Eigen 60 | # Method to hide warnings from eigen using SYSTEM 61 | get_target_property(eigen_include_dirs eigen INTERFACE_INCLUDE_DIRECTORIES) 62 | target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${eigen_include_dirs}) 63 | # -pedantic-errors marl has issues with this 64 | target_link_libraries_system_public(${PROJECT_NAME} marl) 65 | 66 | # Fast Exponential 67 | set(FASTEXP_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/thirdparty") 68 | target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${FASTEXP_INCLUDE_DIR}) 69 | 70 | # Fast PHMAP 71 | get_target_property(phmap_include_dirs phmap INTERFACE_INCLUDE_DIRECTORIES) 72 | target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${phmap_include_dirs}) 73 | 74 | # OPTION Build with OpenMP Support 75 | if (PL_WITH_OPENMP AND NOT APPLE) 76 | find_package(OpenMP) 77 | target_link_libraries(${PROJECT_NAME} PRIVATE OpenMP::OpenMP_CXX) 78 | endif() 79 | 80 | # OPTION Build with Robust Predicates 81 | if (PL_USE_ROBUST_PREDICATES) 82 | MESSAGE(STATUS "Building with Robust Geometric Predicates") 83 | target_link_libraries(${PROJECT_NAME} PRIVATE PL_Predicates) 84 | target_compile_definitions(${PROJECT_NAME} PRIVATE PL_USE_ROBUST_PREDICATES) 85 | endif() 86 | 87 | # Build with FastGA Support 88 | # add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/GaussianAccumulator") 89 | # target_link_libraries(polylidar PRIVATE FastGA::GA) 90 | 91 | 92 | # Set Properties 93 | set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER "${POLYLIDAR_HEADER_FILES}") 94 | set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION} POSITION_INDEPENDENT_CODE ON) 95 | # Set Public and Private Include Directories 96 | target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIR}) 97 | # TODO - This is something that I still do not fully understand, Build vs Install? 98 | target_include_directories(${PROJECT_NAME} PUBLIC 99 | $ 100 | $ 101 | PRIVATE src) 102 | 103 | -------------------------------------------------------------------------------- /src/Polylidar/PLConfig.cpp: -------------------------------------------------------------------------------- 1 | #include "Polylidar/Utility.hpp" 2 | #include "PLConfig.hpp" 3 | 4 | namespace Polylidar { 5 | 6 | std::string GetPolylidarVersion() { return std::string("Polylidar ") + POLYLIDAR_VERSION; } 7 | 8 | bool RobustPredicatesActivated() { return PL_USE_ROBUST_PREDICATES; } 9 | 10 | } // namespace Polylidar -------------------------------------------------------------------------------- /src/Polylidar/PLConfig.hpp.in: -------------------------------------------------------------------------------- 1 | #define POLYLIDAR_VERSION_MAJOR @POLYLIDAR_VERSION_MAJOR@ 2 | #define POLYLIDAR_VERSION_MINOR @POLYLIDAR_VERSION_MINOR@ 3 | #define POLYLIDAR_VERSION_PATCH @POLYLIDAR_VERSION_PATCH@ 4 | #define POLYLIDAR_VERSION "@POLYLIDAR_VERSION@" 5 | 6 | #cmakedefine01 PL_USE_ROBUST_PREDICATES 7 | -------------------------------------------------------------------------------- /src/Python/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Jeremy Castagno 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /src/Python/MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Misc 2 | include README.md 3 | include LICENSE 4 | include requirements.txt 5 | 6 | # Compiled module 7 | include polylidar/polylidar*.pyd 8 | include polylidar/polylidar*.so 9 | include polylidar_pybind*.so 10 | 11 | # Exclude 12 | global-exclude *.py[co] -------------------------------------------------------------------------------- /src/Python/make_install_pip_package.cmake: -------------------------------------------------------------------------------- 1 | # License MIT - Open3D 2 | # We need this file for cross-platform support on Windows 3 | # For Ubuntu/Mac, we can simply do `pip install ${PYTHON_PACKAGE_DST_DIR}/pip_package/*.whl -U` 4 | 5 | # Note: Since `make python-package` clears PYTHON_COMPILED_MODULE_DIR every time, 6 | # it is guaranteed that there is only one wheel in ${PYTHON_PACKAGE_DST_DIR}/pip_package/*.whl 7 | file(GLOB WHEEL_FILE "${PYTHON_PACKAGE_DST_DIR}/pip_package/*.whl") 8 | execute_process(COMMAND pip install ${WHEEL_FILE} -U) -------------------------------------------------------------------------------- /src/Python/make_python_package.cmake: -------------------------------------------------------------------------------- 1 | # Clean up directory 2 | file(REMOVE_RECURSE ${PYTHON_PACKAGE_DST_DIR}) 3 | file(MAKE_DIRECTORY ${PYTHON_PACKAGE_DST_DIR}/polylidar) 4 | 5 | # Create python pacakge. It contains: 6 | # 1) Pure-python code and misc files, copied from src/Python/package 7 | # 2) The compiled python-C++ module, i.e. xxx.so (or the equivalents) 8 | # 3) Configured files and supporting files 9 | 10 | # 1) Pure-python code and misc files, copied from src/Python/package 11 | file(COPY ${PYTHON_PACKAGE_SRC_DIR}/ 12 | DESTINATION ${PYTHON_PACKAGE_DST_DIR} 13 | ) 14 | 15 | # 2) The compiled python-C++ module, i.e. xxx.so (or the equivalents) 16 | get_filename_component(PYTHON_COMPILED_MODULE_NAME ${PYTHON_COMPILED_MODULE_PATH} NAME) 17 | file(COPY ${PYTHON_COMPILED_MODULE_PATH} 18 | DESTINATION ${PYTHON_PACKAGE_DST_DIR}/polylidar) 19 | 20 | MESSAGE("COPYING THIS FILE ${PYTHON_COMPILED_MODULE_PATH} to ${PYTHON_PACKAGE_DST_DIR}/polylidar ") 21 | if (PL_USE_ROBUST_PREDICATES) 22 | MESSAGE(STATUS "Attempting to copy PL_Predicates.dll to python package destination folder") 23 | file(COPY ${ORIG_CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/PL_Predicates.dll 24 | DESTINATION ${PYTHON_PACKAGE_DST_DIR}/polylidar) 25 | endif() 26 | 27 | 28 | 29 | 30 | # 2.5) Copy the compiled module to the parent directory as well, only if this variable is set 31 | if (ORIG_CMAKE_LIBRARY_OUTPUT_DIRECTORY) 32 | file(COPY ${PYTHON_COMPILED_MODULE_PATH} 33 | DESTINATION ${ORIG_CMAKE_LIBRARY_OUTPUT_DIRECTORY}) 34 | endif() 35 | 36 | 37 | # 3) Configured files and supporting files 38 | configure_file("${PYTHON_PACKAGE_SRC_DIR}/setup.py" 39 | "${PYTHON_PACKAGE_DST_DIR}/setup.py") 40 | configure_file("${PYTHON_PACKAGE_SRC_DIR}/polylidar/__init__.py" 41 | "${PYTHON_PACKAGE_DST_DIR}/polylidar/__init__.py") 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/Python/polylidar/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | from .polylidar_pybind import * # py2 py3 compatible 3 | except Exception: 4 | # this was installed with as a python wheel 5 | from polylidar_pybind import * 6 | 7 | try: 8 | import pkg_resources # part of setuptools 9 | __version__ = pkg_resources.require("polylidar")[0].version 10 | except Exception: 11 | __version__ = '@PROJECT_VERSION@' 12 | -------------------------------------------------------------------------------- /src/Python/polylidar/__main__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src/Python/polylidar/__main__.py -------------------------------------------------------------------------------- /src/Python/polylidar/polylidarutil/descartes_patch.py: -------------------------------------------------------------------------------- 1 | """Paths and patches""" 2 | 3 | from matplotlib.patches import PathPatch 4 | from matplotlib.path import Path 5 | from numpy import asarray, concatenate, ones 6 | 7 | 8 | class Polygon(object): 9 | # Adapt Shapely or GeoJSON/geo_interface polygons to a common interface 10 | def __init__(self, context): 11 | if isinstance(context, dict): 12 | self.context = context['coordinates'] 13 | else: 14 | self.context = context 15 | 16 | @property 17 | def exterior(self): 18 | return (getattr(self.context, 'exterior', None) 19 | or self.context[0]) 20 | 21 | @property 22 | def interiors(self): 23 | value = getattr(self.context, 'interiors', None) 24 | if value is None: 25 | value = self.context[1:] 26 | return value 27 | 28 | 29 | def PolygonPath(polygon): 30 | """Constructs a compound matplotlib path from a Shapely or GeoJSON-like 31 | geometric object""" 32 | 33 | def coding(ob): 34 | # The codes will be all "LINETO" commands, except for "MOVETO"s at the 35 | # beginning of each subpath 36 | n = len(getattr(ob, 'coords', None) or ob) 37 | vals = ones(n, dtype=Path.code_type) * Path.LINETO 38 | vals[0] = Path.MOVETO 39 | return vals 40 | 41 | if hasattr(polygon, 'geom_type'): # Shapely 42 | ptype = polygon.geom_type 43 | if ptype == 'Polygon': 44 | polygon = [Polygon(polygon)] 45 | elif ptype == 'MultiPolygon': 46 | polygon = [Polygon(p) for p in polygon] 47 | else: 48 | raise ValueError( 49 | "A polygon or multi-polygon representation is required") 50 | 51 | else: # GeoJSON 52 | polygon = getattr(polygon, '__geo_interface__', polygon) 53 | ptype = polygon["type"] 54 | if ptype == 'Polygon': 55 | polygon = [Polygon(polygon)] 56 | elif ptype == 'MultiPolygon': 57 | polygon = [Polygon(p) for p in polygon['coordinates']] 58 | else: 59 | raise ValueError( 60 | "A polygon or multi-polygon representation is required") 61 | 62 | vertices = concatenate([ 63 | concatenate([asarray(t.exterior.coords)[:, :2]] + 64 | [asarray(r)[:, :2] for r in t.interiors]) 65 | for t in polygon]) 66 | codes = concatenate([ 67 | concatenate([coding(t.exterior.coords)] + 68 | [coding(r) for r in t.interiors]) for t in polygon]) 69 | 70 | return Path(vertices, codes) 71 | 72 | 73 | def PolygonPatch(polygon, **kwargs): 74 | """Constructs a matplotlib patch from a geometric object 75 | 76 | The `polygon` may be a Shapely or GeoJSON-like object with or without holes. 77 | The `kwargs` are those supported by the matplotlib.patches.Polygon class 78 | constructor. Returns an instance of matplotlib.patches.PathPatch. 79 | 80 | Example (using Shapely Point and a matplotlib axes): 81 | 82 | >>> b = Point(0, 0).buffer(1.0) 83 | >>> patch = PolygonPatch(b, fc='blue', ec='blue', alpha=0.5) 84 | >>> axis.add_patch(patch) 85 | 86 | """ 87 | return PathPatch(PolygonPath(polygon), **kwargs) -------------------------------------------------------------------------------- /src/Python/polylidar_pybind/polylidar_pybind.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include // Include first to suppress compiler warnings 7 | #include 8 | #include 9 | 10 | #include "Polylidar/Polylidar.hpp" 11 | #include "polylidar_pybind/docstring/docstring.hpp" 12 | 13 | namespace py = pybind11; 14 | using namespace py::literals; 15 | 16 | PYBIND11_MAKE_OPAQUE(std::vector); 17 | PYBIND11_MAKE_OPAQUE(std::vector); 18 | PYBIND11_MAKE_OPAQUE(std::vector); 19 | PYBIND11_MAKE_OPAQUE(std::vector); 20 | PYBIND11_MAKE_OPAQUE(std::vector>); 21 | PYBIND11_MAKE_OPAQUE(std::vector>); 22 | PYBIND11_MAKE_OPAQUE(std::vector>); 23 | PYBIND11_MAKE_OPAQUE(std::vector>); 24 | 25 | // some helper functions 26 | namespace pybind11 { 27 | namespace detail { 28 | 29 | template 30 | void bind_default_constructor(Class_& cl) 31 | { 32 | cl.def(py::init([]() { return new T(); }), "Default constructor"); 33 | } 34 | 35 | template 36 | void bind_copy_functions(Class_& cl) 37 | { 38 | cl.def(py::init([](const T& cp) { return new T(cp); }), "Copy constructor"); 39 | cl.def("__copy__", [](T& v) { return T(v); }); 40 | cl.def("__deepcopy__", [](T& v, py::dict& memo) { return T(v); }); 41 | } 42 | 43 | } // namespace detail 44 | } // namespace pybind11 -------------------------------------------------------------------------------- /src/Python/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.13.0 2 | shapely 3 | matplotlib 4 | -------------------------------------------------------------------------------- /src/Python/setup.py: -------------------------------------------------------------------------------- 1 | 2 | from setuptools import setup, find_packages 3 | import glob 4 | 5 | # Force platform specific wheel 6 | try: 7 | from wheel.bdist_wheel import bdist_wheel as _bdist_wheel 8 | # https://stackoverflow.com/a/45150383/1255535 9 | 10 | class bdist_wheel(_bdist_wheel): 11 | def finalize_options(self): 12 | _bdist_wheel.finalize_options(self) 13 | self.root_is_pure = False 14 | print("bdist wheel is working and installed") 15 | except ImportError: 16 | print('Warning: cannot import "wheel" package to build platform-specific wheel') 17 | print('Install the "wheel" package to fix this warning') 18 | bdist_wheel = None 19 | 20 | cmdclass = {'bdist_wheel': bdist_wheel} if bdist_wheel is not None else dict() 21 | 22 | 23 | # Read requirements.txt 24 | with open('requirements.txt', 'r') as f: 25 | lines = f.readlines() 26 | install_requires = [line.strip() for line in lines if line] 27 | 28 | 29 | setup( 30 | author='Jeremy Castagno', 31 | author_email='', 32 | classifiers=[ 33 | ], 34 | description=[ 35 | "Polylidar ...." 36 | ], 37 | cmdclass=cmdclass, 38 | install_requires=install_requires, 39 | include_package_data=True, 40 | keywords="", 41 | license="MIT", 42 | long_description=open('README.md').read(), 43 | long_description_content_type='text/markdown', 44 | # Name of the package on PyPI 45 | name="@PYPI_PACKAGE_NAME@", 46 | packages=[ 47 | 'polylidar', 48 | ], 49 | url="@PROJECT_HOME@", 50 | project_urls={ 51 | 'Documentation': '@PROJECT_DOCS@', 52 | 'Source code': '@PROJECT_CODE@', 53 | 'Issues': '@PROJECT_ISSUES@', 54 | }, 55 | version='@PROJECT_VERSION@', 56 | zip_safe=False, 57 | ) 58 | -------------------------------------------------------------------------------- /src/version.txt: -------------------------------------------------------------------------------- 1 | MAJOR 1 2 | MINOR 0 3 | PATCH 9 -------------------------------------------------------------------------------- /src_docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src_docs/.nojekyll -------------------------------------------------------------------------------- /src_docs/_static/cpp_tutorial/opc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src_docs/_static/cpp_tutorial/opc.png -------------------------------------------------------------------------------- /src_docs/_static/cpp_tutorial/opc_mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src_docs/_static/cpp_tutorial/opc_mesh.png -------------------------------------------------------------------------------- /src_docs/_static/cpp_tutorial/opc_polygons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src_docs/_static/cpp_tutorial/opc_polygons.png -------------------------------------------------------------------------------- /src_docs/_static/organized/l515_mesh_smooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src_docs/_static/organized/l515_mesh_smooth.png -------------------------------------------------------------------------------- /src_docs/_static/organized/l515_opc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src_docs/_static/organized/l515_opc.png -------------------------------------------------------------------------------- /src_docs/_static/organized/l515_polygons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src_docs/_static/organized/l515_polygons.png -------------------------------------------------------------------------------- /src_docs/_static/organized/organized_mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src_docs/_static/organized/organized_mesh.png -------------------------------------------------------------------------------- /src_docs/_static/organized/organized_pc_raw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src_docs/_static/organized/organized_pc_raw.png -------------------------------------------------------------------------------- /src_docs/_static/organized/organized_polygons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src_docs/_static/organized/organized_polygons.png -------------------------------------------------------------------------------- /src_docs/_static/pl_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/src_docs/_static/pl_logo.png -------------------------------------------------------------------------------- /src_docs/_static/theme_overrides.css: -------------------------------------------------------------------------------- 1 | /* Force table wrap: https://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html */ 2 | /* override table width restrictions */ 3 | @media screen and (min-width: 767px) { 4 | 5 | .wy-table-responsive table td { 6 | /* !important prevents the common CSS stylesheets from overriding 7 | this as on RTD they are loaded after this stylesheet */ 8 | white-space: normal !important; 9 | } 10 | 11 | .wy-table-responsive { 12 | overflow: visible !important; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src_docs/builddocs.rst: -------------------------------------------------------------------------------- 1 | .. _builddocs: 2 | 3 | Building Documentation 4 | ====================== 5 | 6 | The main documentation and Python documentation is written in 7 | `reStructuredText `_ and 8 | generated by `sphinx `_. The C++ API documentation 9 | is generated as xml with `Doxygen `_ and parsed by 10 | `breathe `_ and `exhale `_. 11 | 12 | 13 | Prerequisites 14 | ------------- 15 | 16 | 1. Build Polylidar3D from source 17 | `````````````````````````````````````` 18 | 19 | Clone and build Polylidar from source: :ref:`install_instructions`. 20 | 21 | 2. Install Sphinx 22 | ````````````````` 23 | 24 | .. code-block:: bash 25 | 26 | # For pip 27 | pip install sphinx sphinx-autobuild 28 | 29 | # Or, for Conda 30 | conda install sphinx sphinx-autobuild 31 | 32 | 33 | 3. Install Doxygen 34 | `````````````````` 35 | 36 | **Ubuntu** 37 | 38 | .. code-block:: bash 39 | 40 | sudo apt-get -y install doxygen 41 | 42 | **macOS** 43 | 44 | .. code-block:: bash 45 | 46 | brew install doxygen 47 | 48 | **Windows** 49 | 50 | Visit `Doxygen downloads `_ page to 51 | checkout the source or 52 | `binaries `_. 53 | 54 | 55 | Build 56 | ----- 57 | 58 | .. code-block:: bash 59 | 60 | cd docs 61 | 62 | # You may optionally select the --sphinx flag 63 | python make_docs.py --sphinx 64 | 65 | The docs html will be saved in ``docs/_out`` folder. 66 | -------------------------------------------------------------------------------- /src_docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Polylidar3D documentation master file, created by 2 | sphinx-quickstart on Mon Apr 3 14:18:28 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. image:: _static/pl_logo.png 7 | :alt: Polylidar3D Logo 8 | :width: 320px 9 | :align: center 10 | 11 | ----------- 12 | 13 | Polylidar3D: Fast Polygon Extraction from 2D & 3D Data 14 | ======================================================== 15 | 16 | .. only: not latex 17 | 18 | Contents: 19 | 20 | .. _gettting_started_index: 21 | 22 | .. toctree:: 23 | :maxdepth: 1 24 | :caption: Getting Started 25 | 26 | introduction 27 | install_instructions 28 | builddocs 29 | 30 | 31 | .. _tutorial_index: 32 | 33 | .. toctree:: 34 | :maxdepth: 1 35 | :caption: Tutorial 36 | 37 | tutorial/Python/index 38 | tutorial/C++/index 39 | 40 | .. _python_api_index: 41 | 42 | .. toctree:: 43 | :maxdepth: 1 44 | :caption: Python API 45 | 46 | python_api/polylidar 47 | python_api/polylidar.polylidarutil.plane_filtering 48 | 49 | .. 50 | Please put the module and meta data you want here! 51 | MAKE_DOCS/python_api/polylidar 52 | MAKE_DOCS/python_api/polylidar.polylidarutil.plane_filtering python_only 53 | 54 | .. _cpp_api_index: 55 | 56 | .. toctree:: 57 | :maxdepth: 1 58 | :caption: C++ API 59 | 60 | cpp_api/cpp_library_root 61 | -------------------------------------------------------------------------------- /src_docs/introduction.rst: -------------------------------------------------------------------------------- 1 | .. _introduction: 2 | 3 | Introducing Polylidar3D 4 | ############################ 5 | 6 | 7 | .. mdinclude:: ../README.md 8 | 9 | -------------------------------------------------------------------------------- /src_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 | 11 | %SPHINXBUILD% >NUL 2>NUL 12 | if errorlevel 9009 ( 13 | echo. 14 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 15 | echo.installed, then set the SPHINXBUILD environment variable to point 16 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 17 | echo.may add the Sphinx directory to PATH. 18 | echo. 19 | echo.If you don't have Sphinx installed, grab it from 20 | echo.http://sphinx-doc.org/ 21 | exit /b 1 22 | ) 23 | 24 | python make.py %1 25 | goto end 26 | 27 | :end 28 | popd 29 | -------------------------------------------------------------------------------- /src_docs/tutorial/C++/basic.rst: -------------------------------------------------------------------------------- 1 | .. _cplusplus_basic_tutorial: 2 | 3 | C++ interface 4 | ------------------ 5 | 6 | Take a look at `examples/cpp/polylidar-full-example.cpp`. 7 | 8 | First we load a numpy file of an organized point cloud that has been previosly captured. 9 | 10 | .. literalinclude:: ../../../examples/cpp/polylidar-full-example.cpp 11 | :language: cpp 12 | :lines: 42-64 13 | 14 | Then we convert the vector to a matrix wrapper (no copy) that Polylidar uses. 15 | 16 | .. literalinclude:: ../../../examples/cpp/polylidar-full-example.cpp 17 | :language: cpp 18 | :lines: 66-71 19 | 20 | Then we visualize the point cloud: 21 | 22 | .. literalinclude:: ../../../examples/cpp/polylidar-full-example.cpp 23 | :language: cpp 24 | :lines: 73-77 25 | 26 | .. image:: /_static/cpp_tutorial/opc.png 27 | 28 | 29 | Then we create and visualize the mesh: 30 | 31 | .. literalinclude:: ../../../examples/cpp/polylidar-full-example.cpp 32 | :language: cpp 33 | :lines: 79-92 34 | 35 | .. image:: /_static/cpp_tutorial/opc_mesh.png 36 | 37 | 38 | Then we find the dominant plane normals using FastGA: 39 | 40 | .. literalinclude:: ../../../examples/cpp/polylidar-full-example.cpp 41 | :language: cpp 42 | :lines: 94-99 43 | 44 | 45 | Then we extract the Planes and Polygons: 46 | 47 | .. literalinclude:: ../../../examples/cpp/polylidar-full-example.cpp 48 | :language: cpp 49 | :lines: 101-107 50 | 51 | 52 | Then we colorize the Open3D mesh by plane segments and create the corresponding polygons: 53 | 54 | .. literalinclude:: ../../../examples/cpp/polylidar-full-example.cpp 55 | :language: cpp 56 | :lines: 109-133 57 | 58 | .. image:: /_static/cpp_tutorial/opc_polygons.png 59 | 60 | 61 | Full output: 62 | 63 | .. code-block:: bash 64 | 65 | $ ./cmake-build/bin/Release/polylidar-full.exe 66 | A more complete example of using Polylidar3D with 3D Data. Needs Open3D and FastGA. 67 | Loading previously captured Organized Point Cloud from an L515 Camera. 68 | Attempting to load OPC file from ./fixtures/realsense/opc_example_one/L515_OPC.npy 69 | Shape of Point Cloud is: [180, 320, 3] 70 | Visualizing Point Cloud. Rotate View. Close Open3D window when satisfied... 71 | Creating Mesh and smoothing. NOT using optimized filters, see OrganizedPointFilters 72 | Visualizing mesh. Rotate View. 73 | Detected Peaks with FastGaussianAccumulator. Here are the detected peaks: [0.0641286, 0.525564, -0.848333], [0.00935028, -0.849775, -0.527063], 74 | 75 | Extracting Polygons from all detected peaks: 76 | Visualing Planes and Polygon. Each plane segment is color coded. Polgyons are shown as thin lines (OpenGL has no thickness): -------------------------------------------------------------------------------- /src_docs/tutorial/C++/index.rst: -------------------------------------------------------------------------------- 1 | C++ 2 | =================================================================== 3 | 4 | C++ Interface Tutorials 5 | 6 | .. toctree:: 7 | 8 | basic -------------------------------------------------------------------------------- /src_docs/tutorial/Python/basicdemo.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../../examples/python/notebooks/BasicDemo.ipynb" 3 | } -------------------------------------------------------------------------------- /src_docs/tutorial/Python/index.rst: -------------------------------------------------------------------------------- 1 | Python 2 | =================================================================== 3 | 4 | Python Tutorials 5 | 6 | .. toctree:: 7 | :maxdepth: 0 8 | 9 | basicdemo 10 | organizeddemo 11 | 12 | 13 | .. warning:: 14 | Polylidar3D uses Open3D to visualize 3D geometries (pointclouds, polygons, meshes, etc.). A recent version of Open3D has a serious performance regression which causes severe slowdown during visualization. 15 | This regression is on versions 0.10 and 0.11 of Open3D. Regression details: `Link `_ , `Issue1 `_ , `Issue2 `_. 16 | I recommend that you stick with 0.9.0, build from master, or wait for 0.12. -------------------------------------------------------------------------------- /src_docs/tutorial/Python/organizeddemo.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../../examples/python/notebooks/OrganizedDemo.ipynb" 3 | } -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(cpp) -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/tests/__init__.py -------------------------------------------------------------------------------- /tests/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | FetchContent_Declare( 3 | extern_doctest 4 | GIT_REPOSITORY https://github.com/onqtam/doctest.git 5 | GIT_TAG 2.3.7 6 | ) 7 | 8 | # This module depend on having doctest installed 9 | FetchContent_MakeAvailable(extern_doctest) 10 | 11 | add_executable(run-tests ${CMAKE_CURRENT_SOURCE_DIR}/run-tests.cpp) 12 | target_link_libraries(run-tests PRIVATE doctest::doctest Polylidar::PL) 13 | -------------------------------------------------------------------------------- /tests/cpp/run-tests.cpp: -------------------------------------------------------------------------------- 1 | // This file configured doctest to run, please leave empty 2 | // Write more tests as separate files and add in CMakeLists.txt 3 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 4 | #include "doctest/doctest.h" 5 | 6 | -------------------------------------------------------------------------------- /tests/python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/tests/python/__init__.py -------------------------------------------------------------------------------- /tests/python/benchmark_test.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | import pytest 3 | import numpy as np 4 | 5 | 6 | from tests.python.helpers.utils import load_csv, verify_points, basic_polylidar_verification, verify_all_polygons_are_valid, load_npy 7 | try: 8 | from polylidar import extractPlanesAndPolygons, extractPolygons, extractPolygonsAndTimings 9 | Polylidar3D = None 10 | except Exception: 11 | print("Using new Polylidar3D") 12 | from polylidar import Polylidar3D, MatrixDouble 13 | 14 | 15 | # def test_building1(benchmark, building1, basic_params): 16 | # benchmark(extractPlanesAndPolygons, building1, **basic_params) 17 | 18 | 19 | # def test_building2(benchmark, building2, basic_params): 20 | # benchmark(extractPlanesAndPolygons, building2, **basic_params) 21 | 22 | 23 | def test_100k_array_lmax(benchmark, np_100K_array, params_lmax): 24 | if Polylidar3D is not None: 25 | points_mat = MatrixDouble(np_100K_array) 26 | pl = Polylidar3D(**params_lmax) 27 | _, _ , _ = benchmark(pl.extract_planes_and_polygons, points_mat) 28 | else: 29 | benchmark(extractPolygons, np_100K_array, **params_lmax) 30 | 31 | def test_100k_array_3d_lmax(benchmark, np_100K_array_3d, params_lmax): 32 | if Polylidar3D is not None: 33 | points_mat = MatrixDouble(np_100K_array_3d) 34 | pl = Polylidar3D(**params_lmax) 35 | _, _ , _ = benchmark(pl.extract_planes_and_polygons, points_mat) 36 | else: 37 | benchmark(extractPolygons, np_100K_array_3d, **params_lmax) 38 | 39 | def test_clusters(benchmark, cluster_groups): 40 | """ 41 | Will benchmark clusters AND check that python overhead is less than 3ms (ussually <1 ms) 42 | """ 43 | points = cluster_groups['points'] 44 | polylidar_kwargs = cluster_groups['polylidar_kwargs'] 45 | if Polylidar3D is not None: 46 | points_mat = MatrixDouble(points) 47 | pl = Polylidar3D(**polylidar_kwargs) 48 | _, _ , _ = benchmark(pl.extract_planes_and_polygons, points_mat) 49 | else: 50 | _, _ , _ = benchmark(extractPlanesAndPolygons, points, **polylidar_kwargs) 51 | 52 | 53 | # def test_clusters(benchmark, cluster_groups): 54 | # """ 55 | # Will benchmark clusters AND check that python overhead is less than 3ms (ussually <1 ms) 56 | # """ 57 | # points = cluster_groups['points'] 58 | # polylidar_kwargs = cluster_groups['polylidar_kwargs'] 59 | # _, timings = benchmark(extractPolygonsAndTimings, points, **polylidar_kwargs) 60 | # cpp_timing = np.sum(timings) / 1000.0 # Actuall c++ timing 61 | # median_benchmark = benchmark.stats.stats.median # python benchmark timing 62 | # assert cpp_timing == pytest.approx(median_benchmark, abs=0.003) 63 | 64 | -------------------------------------------------------------------------------- /tests/python/conftest.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | import pytest 3 | import numpy as np 4 | 5 | 6 | from tests.python.helpers.utils import load_csv, load_npy 7 | from polylidar.polylidarutil import generate_test_points, get_estimated_lmax 8 | 9 | np.random.seed(1) 10 | 11 | 12 | @pytest.fixture 13 | def building1(): 14 | return load_csv('building1.csv') 15 | 16 | 17 | @pytest.fixture 18 | def building2(): 19 | return load_csv("building2.csv") 20 | 21 | 22 | @pytest.fixture 23 | def hardcase1(): 24 | return load_csv("hardcase1.csv") 25 | 26 | 27 | # @pytest.fixture 28 | # def bad_convex_hull(): 29 | # return load_npy("possible_error_free.npy") 30 | 31 | 32 | @pytest.fixture() 33 | def basic_params(): 34 | return dict(alpha=0.5) 35 | 36 | 37 | @pytest.fixture() 38 | def params_lmax(): 39 | return dict(alpha=0.0, lmax=100.0) 40 | 41 | 42 | @pytest.fixture() 43 | def hardcase1_params(): 44 | return dict(alpha=0.0, lmax=20.0) 45 | 46 | 47 | @pytest.fixture 48 | def np_100K_array(): 49 | return load_csv("100K_array_2d.csv") 50 | 51 | 52 | @pytest.fixture 53 | def np_100K_array_3d(): 54 | return load_csv("100K_array_3d.csv") 55 | 56 | 57 | @pytest.fixture() 58 | def bad_convex_hull_params(): 59 | return dict(alpha=0.0, lmax=1300.0) 60 | 61 | @pytest.fixture(params=range(2, 11)) 62 | def num_groups(request): 63 | return request.param 64 | 65 | @pytest.fixture(params=[1000, 10000]) 66 | def group_size(request): 67 | return request.param 68 | 69 | @pytest.fixture 70 | def cluster_groups(num_groups, group_size): 71 | cluster_params = dict(num_groups=num_groups, group_size=group_size) 72 | points = generate_test_points(**cluster_params) 73 | lmax = get_estimated_lmax(**cluster_params) 74 | polylidar_kwargs = dict(alpha=0.0, lmax=lmax) 75 | return dict(points=points, polylidar_kwargs=polylidar_kwargs, cluster_params=cluster_params) 76 | 77 | # This creates 100 numpy arrays frangin from (1000,2) -> (100000,2) 78 | ts = range(1000, 100000, 1000) 79 | @pytest.fixture(params=ts) 80 | def random_points(request): 81 | points = np.random.randn(request.param, 2) * 100 + 700000 82 | return points 83 | -------------------------------------------------------------------------------- /tests/python/generated_test.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | import pytest 3 | import numpy as np 4 | 5 | 6 | from tests.python.helpers.utils import verify_points, basic_polylidar_verification, verify_all_polygons_are_valid, verify_all 7 | 8 | 9 | np.random.seed(1) 10 | 11 | def test_random_points(random_points, params_lmax): 12 | verify_all(random_points, params_lmax) 13 | 14 | def test_clusters(cluster_groups): 15 | points = cluster_groups['points'] 16 | polylidar_kwargs = cluster_groups['polylidar_kwargs'] 17 | verify_all(points, polylidar_kwargs) 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/python/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeremyBYU/polylidar/e64fd96274786f9126eb8597afe9a8fd40a823d2/tests/python/helpers/__init__.py -------------------------------------------------------------------------------- /tests/python/helpers/utils.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | import pytest 3 | import numpy as np 4 | 5 | from shapely.geometry import Polygon 6 | from polylidar import Polylidar3D, MatrixDouble, HalfEdgeTriangulation 7 | 8 | DIR_NAME = path.dirname(path.dirname(path.dirname(path.dirname(__file__)))) 9 | FIXTURES_DIR = path.join(DIR_NAME, 'fixtures', 'pointsets') 10 | 11 | def get_poly_coords(outline, points): 12 | return [get_point(pi, points) for pi in outline] 13 | 14 | def get_point(pi, points): 15 | return [points[pi, 0], points[pi, 1]] 16 | 17 | def load_csv(file, delimeter=','): 18 | return np.loadtxt(path.join(FIXTURES_DIR, file), delimiter=delimeter, dtype=np.float64, skiprows=1) 19 | 20 | def load_npy(file): 21 | return np.load(path.join(FIXTURES_DIR, file)) 22 | 23 | def verify_points(data, n_cols=2): 24 | assert isinstance(data, np.ndarray) 25 | assert data.shape[1] == n_cols 26 | 27 | def basic_polylidar_verification(points, mesh:HalfEdgeTriangulation, planes, polygons): 28 | # make sure delaunay has the correct number of points 29 | assert points.shape[0] == np.array(mesh.vertices).shape[0] 30 | # make sure that some planes were extracted 31 | assert len(planes) > 0 32 | # make sure that some polygons were extracted 33 | assert len(polygons) > 0 34 | 35 | def verify_all_polygons_are_valid(polygons, points): 36 | for poly in polygons: 37 | verify_valid_polygon(poly, points) 38 | 39 | def verify_valid_polygon(poly, points): 40 | shell_coords = get_poly_coords(poly.shell, points) 41 | hole_coords = [get_poly_coords(hole, points) for hole in poly.holes] 42 | # Verify polygon outer shell 43 | shapelyPoly = Polygon(shell=shell_coords) 44 | assert shapelyPoly.is_valid 45 | # Verify polygon outer shell and holes 46 | shapelyPoly = Polygon(shell=shell_coords, holes=hole_coords) 47 | # if not shapelyPoly.is_valid: 48 | # np.save("scratch/error_{}.npy".format(points.shape[0]), points) 49 | assert shapelyPoly.is_valid 50 | 51 | def verify_all(points, polylidar_kwargs): 52 | pl = Polylidar3D(**polylidar_kwargs) 53 | points_mat = MatrixDouble(points) 54 | mesh, planes, polygons = pl.extract_planes_and_polygons(points_mat) 55 | # Basic test to ensure no obvious errors occurred 56 | basic_polylidar_verification(points, mesh, planes, polygons) 57 | # Ensure that the polygons returned are valid 58 | verify_all_polygons_are_valid(polygons, points) 59 | 60 | 61 | -------------------------------------------------------------------------------- /tests/python/shape_test.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | import pytest 3 | import numpy as np 4 | 5 | 6 | from tests.python.helpers.utils import verify_points, basic_polylidar_verification, verify_all_polygons_are_valid, verify_all 7 | 8 | 9 | np.random.seed(1) 10 | 11 | def test_verify_building1(building1): 12 | verify_points(building1, 4) 13 | 14 | def test_verify_building2(building2): 15 | verify_points(building2, 4) 16 | 17 | def test_verify_hardcase1(hardcase1): 18 | verify_points(hardcase1, 2) 19 | 20 | def test_building1(building1, basic_params): 21 | verify_all(building1, basic_params) 22 | 23 | 24 | def test_building2(building2, basic_params): 25 | verify_all(building2, basic_params) 26 | 27 | def test_hardcase1(hardcase1, hardcase1_params): 28 | verify_all(hardcase1, hardcase1_params) 29 | 30 | # def test_bad_convex_hull(bad_convex_hull, bad_convex_hull_params): 31 | # verify_all(bad_convex_hull, bad_convex_hull_params) 32 | 33 | def test_random_points(random_points, params_lmax): 34 | verify_all(random_points, params_lmax) 35 | 36 | def test_clusters(cluster_groups): 37 | points = cluster_groups['points'] 38 | polylidar_kwargs = cluster_groups['polylidar_kwargs'] 39 | verify_all(points, polylidar_kwargs) 40 | 41 | 42 | -------------------------------------------------------------------------------- /thirdparty/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | # Put any LIBRARY dependencies in here 3 | FetchContent_Declare( 4 | extern_pybind11 5 | GIT_REPOSITORY https://github.com/pybind/pybind11.git 6 | GIT_TAG v2.9.1 7 | GIT_PROGRESS TRUE 8 | ) 9 | 10 | FetchContent_Declare( 11 | extern_parallel_hashmap 12 | GIT_REPOSITORY https://github.com/greg7mdp/parallel-hashmap.git 13 | GIT_TAG 1.31 14 | GIT_PROGRESS TRUE 15 | ) 16 | 17 | FetchContent_Declare( 18 | extern_eigen 19 | GIT_REPOSITORY https://github.com/eigenteam/eigen-git-mirror.git 20 | GIT_TAG 3.3.7 21 | GIT_PROGRESS TRUE 22 | ) 23 | 24 | FetchContent_Declare( 25 | extern_marl 26 | GIT_REPOSITORY https://github.com/google/marl.git 27 | GIT_TAG 32af8bb50807f719818168eb165761e60e0090d5 28 | GIT_PROGRESS TRUE 29 | ) 30 | 31 | FetchContent_Declare( 32 | extern_fastga 33 | GIT_REPOSITORY https://github.com/JeremyBYU/FastGaussianAccumulator 34 | GIT_TAG origin/master 35 | GIT_PROGRESS TRUE 36 | ) 37 | 38 | 39 | # Build Robust Predicates if requested 40 | if (PL_USE_ROBUST_PREDICATES) 41 | MESSAGE(STATUS "Building Robust Geometric Predicates") 42 | add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/predicates") 43 | endif() 44 | 45 | # After the following call, the library will be installed 46 | FetchContent_MakeAvailable(extern_parallel_hashmap extern_pybind11 extern_eigen extern_marl) 47 | 48 | # Build Robust Predicates if requested 49 | if (PL_BUILD_FASTGA AND PL_BUILD_EXAMPLES) 50 | MESSAGE(STATUS "Building Examples with FastGA") 51 | # Add fastga but dont build examples and unit tests 52 | FetchContent_GetProperties(extern_fastga) 53 | if(NOT extern_fastga_POPULATED) 54 | FetchContent_Populate(extern_fastga) 55 | set(GA_BUILD_PYMODULE OFF CACHE BOOL "GA -Build Python Module" FORCE) 56 | set(GA_BUILD_TESTS OFF CACHE BOOL "GA - Build Tests" FORCE) 57 | set(GA_BUILD_BENCHMARKS OFF CACHE BOOL "GA - Build Benchmarks" FORCE) 58 | set(GA_BUILD_EXAMPLES OFF CACHE BOOL "GA - Build Examples" FORCE) 59 | add_subdirectory(${extern_fastga_SOURCE_DIR} ${extern_fastga_BINARY_DIR}) 60 | endif() 61 | endif() 62 | -------------------------------------------------------------------------------- /thirdparty/FastExp/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 simonpf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /thirdparty/FastExp/fastexp.h: -------------------------------------------------------------------------------- 1 | #ifndef FASTEXP_H 2 | #define FASTEXP_H 3 | 4 | #include "math.h" 5 | #include 6 | #include 7 | #include 8 | #include "FastExp/product.h" 9 | #include "FastExp/ieee.h" 10 | 11 | namespace fastexp 12 | { 13 | 14 | enum class Approximation {IEEE, PRODUCT}; 15 | 16 | /** \brief Fast approximate exponential. 17 | * 18 | * This function implements a fast, vectorizable approximation 19 | * of the exponential function based on the following two articles: 20 | * 21 | * - Malossi, A. Cristiano I. & Ineichen, Yves & Bekas, Costas & Curioni, 22 | * Alessandro. "Fast Exponential Computation on SIMD Architectures." (2015) 23 | * 10.13140/2.1.4362.3207. 24 | * - IEEE, Nicol N. "A fast, compact approximation of the exponential 25 | * function." Neural Computation 11.4 (1999): 853-862. 26 | * 27 | * The approximation interpolates linearly between points on the curve of 28 | * the exponential function that can be expressed as 2^i where i is an 29 | * a signed integer. So yes, that is very approximate ... 30 | * 31 | * \tparam Real The floating point type of the arguments. 32 | * \param x The argument of the exponential function. 33 | * \return The approximated value of the exponential function. 34 | */ 35 | #ifndef _WIN32 36 | #pragma omp declare simd notinbranch 37 | #endif 38 | template 39 | < 40 | typename Real, 41 | template class Approximation = IEEE, 42 | size_t degree = 2 43 | > 44 | inline Real exp(const Real &x) 45 | { 46 | return Approximation::evaluate(x); 47 | } 48 | 49 | /** \brief Fast approximate array exponential. 50 | * 51 | * Applies the fast exponential to an array of given length making 52 | * use of SIMD instructions if available. To enable vectorization 53 | * the code needs to be compiled with OpenMP support. 54 | * 55 | * \tparam Real The floating point type of the arguments. 56 | * \param x The array to which apply the exponential function. 57 | * \return n The number of elements in the array. 58 | */ 59 | template 60 | < 61 | typename Real, 62 | template class Approximation = IEEE, 63 | size_t degree = 2 64 | > 65 | inline void exp(Real *x, size_t n) { 66 | // Vectorized part. 67 | #pragma omp simd 68 | for (size_t i = 0; i < n; ++i) { 69 | Real e = fastexp::exp(x[i]); 70 | x[i] = e; 71 | } 72 | } 73 | 74 | template 75 | < 76 | typename Real, 77 | template class Approximation = IEEE, 78 | size_t degree = 2 79 | > 80 | inline void exp(std::vector x) { 81 | // Vectorized part. 82 | size_t n = x.size(); 83 | Real * x_ptr = &x[0]; 84 | 85 | #pragma omp simd 86 | for (size_t i = 0; i < n; ++i) { 87 | Real e = fastexp::exp(x_ptr[i]); 88 | x_ptr[i] = e; 89 | } 90 | } 91 | 92 | } // fastexp 93 | #endif // FASTEXP_H 94 | -------------------------------------------------------------------------------- /thirdparty/FastExp/product.h: -------------------------------------------------------------------------------- 1 | #ifndef FASTEXP_PRODUCT_H 2 | #define FASTEXP_PRODUCT_H 3 | 4 | #include "math.h" 5 | #include 6 | 7 | namespace fastexp { 8 | 9 | template 10 | struct Recursion { 11 | static Real evaluate(Real x) { 12 | constexpr Real c = 1.0 / static_cast(1u << degree); 13 | x = Recursion::evaluate(x); 14 | return x * x; 15 | } 16 | }; 17 | 18 | template 19 | struct Recursion { 20 | static Real evaluate(Real x) { 21 | constexpr Real c = 1.0 / static_cast(1u << degree); 22 | x = 1.0 + c * x; 23 | return x; 24 | } 25 | }; 26 | 27 | template 28 | struct Product { 29 | static Real evaluate(Real x) { 30 | return Recursion::evaluate(x); 31 | } 32 | }; 33 | 34 | 35 | } // fastexp 36 | #endif // FASTEXP_PRODUCT_H 37 | -------------------------------------------------------------------------------- /thirdparty/predicates/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.0) 2 | project(PL_Predicates) 3 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -std=c++14") 4 | 5 | set(LIB_TYPE SHARED) 6 | set (PL_predicates_SRCS 7 | ${CMAKE_CURRENT_SOURCE_DIR}/constants.c 8 | ${CMAKE_CURRENT_SOURCE_DIR}/predicates.c 9 | ${CMAKE_CURRENT_SOURCE_DIR}/random.c 10 | ${CMAKE_CURRENT_SOURCE_DIR}/printing.c 11 | ) 12 | 13 | set(PL_predicates_HDRS 14 | ${CMAKE_CURRENT_SOURCE_DIR}/constants.h 15 | ${CMAKE_CURRENT_SOURCE_DIR}/predicates.h 16 | ${CMAKE_CURRENT_SOURCE_DIR}/predicates.hpp 17 | ${CMAKE_CURRENT_SOURCE_DIR}/predicatesDLLExport.h 18 | ) 19 | 20 | set(PL_predicates_header_dir ${CMAKE_CURRENT_SOURCE_DIR}) 21 | add_library (PL_Predicates ${LIB_TYPE} ${PL_predicates_SRCS} ${PL_predicates_HDRS}) 22 | 23 | target_include_directories(PL_Predicates PUBLIC 24 | $ 25 | $ 26 | PRIVATE src) 27 | # set_property(TARGET predicates PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) 28 | -------------------------------------------------------------------------------- /thirdparty/predicates/README.md: -------------------------------------------------------------------------------- 1 | All source code in this directory is from the github repository: https://github.com/danshapero/predicates 2 | 3 | Here is an excerpt of the repository README.md on licensing: 4 | 5 | This code has not been released under a well-known free software license such as the BSD License or the GNU Public License. Instead, Shewchuk released the original predicates code stating only that it was "Placed in the public domain". Lawrence Rosen, general counsel for the Open Source Initiative, has argued that public domain is not a license. I am not a lawyer, but if you're thinking of using this code for proprietary software you should consult a lawyer first. I do not own the copyright on this code and cannot grant a license to use it. -------------------------------------------------------------------------------- /thirdparty/predicates/constants.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | /** 5 | * Different sources define the "machine epsilon" in one of two ways. 6 | * The C standard library header `float.h` defines DBL_EPSILON, such that 7 | * 1.0 + DBL_EPSILON > 1.0 8 | * in double-precision arithmetic, but such that 9 | * 1.0 + z = 1.0 10 | * for any `z < DBL_EPSILON`. Many other sources, included Shewchuk's original 11 | * code and the supporting paper, define the machine epsilon instead as the 12 | * largest floating-point number such that 13 | * 1.0 + epsilon = 1.0 14 | * The two are related by 15 | * epsilon = DBL_EPSILON / 2. 16 | */ 17 | #define EPS (DBL_EPSILON / 2) 18 | 19 | const double epsilon = EPS; 20 | const double splitter = ((DBL_MANT_DIG + 1) >> 1) + 1.0; 21 | 22 | const double resulterrbound = (3.0 + 8.0 * EPS) * EPS; 23 | 24 | const double ccwerrboundA = (3.0 + 16.0 * EPS) * EPS; 25 | const double ccwerrboundB = (2.0 + 12.0 * EPS) * EPS; 26 | const double ccwerrboundC = (9.0 + 64.0 * EPS) * EPS * EPS; 27 | const double o3derrboundA = (7.0 + 56.0 * EPS) * EPS; 28 | const double o3derrboundB = (3.0 + 28.0 * EPS) * EPS; 29 | const double o3derrboundC = (26.0 + 288.0 * EPS) * EPS * EPS; 30 | const double iccerrboundA = (10.0 + 96.0 * EPS) * EPS; 31 | const double iccerrboundB = (4.0 + 48.0 * EPS) * EPS; 32 | const double iccerrboundC = (44.0 + 576.0 * EPS) * EPS * EPS; 33 | const double isperrboundA = (16.0 + 224.0 * EPS) * EPS; 34 | const double isperrboundB = (5.0 + 72.0 * EPS) * EPS; 35 | const double isperrboundC = (71.0 + 1408.0 * EPS) * EPS * EPS; 36 | -------------------------------------------------------------------------------- /thirdparty/predicates/constants.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CONSTANTS_H 3 | #define CONSTANTS_H 4 | 5 | extern const double epsilon; 6 | extern const double splitter; 7 | 8 | extern const double resulterrbound; 9 | 10 | extern const double ccwerrboundA; 11 | extern const double ccwerrboundB; 12 | extern const double ccwerrboundC; 13 | 14 | extern const double o3derrboundA; 15 | extern const double o3derrboundB; 16 | extern const double o3derrboundC; 17 | 18 | extern const double iccerrboundA; 19 | extern const double iccerrboundB; 20 | extern const double iccerrboundC; 21 | 22 | extern const double isperrboundA; 23 | extern const double isperrboundB; 24 | extern const double isperrboundC; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /thirdparty/predicates/predicates.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef PREDICATES_H 3 | #define PREDICATES_H 4 | 5 | #include 6 | 7 | #include "predicatesDLLExport.h" 8 | 9 | #ifdef __cplusplus 10 | extern "C" 11 | { 12 | namespace predicates { 13 | #endif 14 | 15 | /** 16 | * Robust, adaptive-precision geometric predicates 17 | */ 18 | 19 | PREDICATES_EXPORT 20 | double orient2d(const double * pa, const double * pb, const double * pc); 21 | 22 | PREDICATES_EXPORT 23 | double orient3d(const double * pa, 24 | const double * pb, 25 | const double * pc, 26 | const double * pd); 27 | 28 | PREDICATES_EXPORT 29 | double incircle(const double * pa, 30 | const double * pb, 31 | const double * pc, 32 | const double * pd); 33 | 34 | PREDICATES_EXPORT 35 | double insphere(const double * pa, 36 | const double * pb, 37 | const double * pc, 38 | const double * pd, 39 | const double * pe); 40 | 41 | 42 | /** 43 | * Nonrobust, fast geometric predicates. 44 | */ 45 | 46 | PREDICATES_EXPORT 47 | double orient2dfast(const double * pa, const double * pb, const double * pc); 48 | 49 | PREDICATES_EXPORT 50 | double orient3dfast(const double * pa, 51 | const double * pb, 52 | const double * pc, 53 | const double * pd); 54 | 55 | PREDICATES_EXPORT 56 | double incirclefast(const double * pa, 57 | const double * pb, 58 | const double * pc, 59 | const double * pd); 60 | 61 | PREDICATES_EXPORT 62 | double inspherefast(const double * pa, 63 | const double * pb, 64 | const double * pc, 65 | const double * pd, 66 | const double * pe); 67 | 68 | #ifdef __cplusplus 69 | } // end of namespace predicates 70 | 71 | } // end of extern "C" 72 | #endif 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /thirdparty/predicates/predicates.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef PREDICATES_HPP 3 | #define PREDICATES_HPP 4 | 5 | 6 | #include 7 | 8 | #include "predicates.h" 9 | 10 | namespace predicates { 11 | 12 | template 13 | void perturb2d( 14 | const Predicate& predicate, 15 | const double * x0, 16 | const size_t nx, 17 | const size_t ny, 18 | Continuation& continuation 19 | ) 20 | { 21 | double x[] = {x0[0], x0[1]}; 22 | 23 | for (size_t i = 0; i < ny; ++i) { 24 | x[0] = x0[0]; 25 | 26 | for (size_t j = 0; j < nx; ++j) { 27 | const double p = predicate(x); 28 | continuation(p, x, i, j); 29 | 30 | x[0] = nextafter(x[0], x[0] + 1); 31 | } 32 | x[1] = nextafter(x[1], x[1] + 1); 33 | } 34 | } 35 | 36 | 37 | } // end of namespace predicates 38 | 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /thirdparty/predicates/predicatesDLLExport.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef _predicatesdllexport_h_ 4 | #define _predicatesdllexport_h_ 5 | 6 | 7 | #if defined (_MSC_VER) 8 | //#pragma warning(disable: 4267) 9 | //#pragma warning(disable: 4800) /* warning C4800: 'double' : forcing value to bool 'true' or 'false' */ 10 | #endif 11 | 12 | 13 | /* Cmake will define predicates_EXPORTS on Windows when it 14 | configures to build a shared library. If you are going to use 15 | another build system on windows or create the visual studio 16 | projects by hand you need to define predicates_EXPORTS when 17 | building the predicates DLL on windows. 18 | */ 19 | 20 | 21 | #if defined (predicates_EXPORTS) /* Compiling the predicates DLL/Dylib */ 22 | #if defined (_MSC_VER) /* MSVC Compiler Case */ 23 | #define PREDICATES_EXPORT __declspec(dllexport) 24 | #define EXPIMP_TEMPLATE 25 | #elif (__GNUC__ >= 4) /* GCC 4.x has support for visibility options */ 26 | #define PREDICATES_EXPORT __attribute__ ((visibility("default"))) 27 | #endif 28 | #else /* Importing the DLL into another project */ 29 | #if defined (_MSC_VER) /* MSVC Compiler Case */ 30 | #define PREDICATES_EXPORT __declspec(dllimport) 31 | #define EXPIMP_TEMPLATE extern 32 | #elif (__GNUC__ >= 4) /* GCC 4.x has support for visibility options */ 33 | #define PREDICATES_EXPORT __attribute__ ((visibility("default"))) 34 | #endif 35 | #endif 36 | 37 | 38 | /* If PREDICATES_EXPORT was never defined, define it here */ 39 | #ifndef PREDICATES_EXPORT 40 | #define PREDICATES_EXPORT 41 | #define EXPIMP_TEMPLATE 42 | #endif 43 | 44 | #endif /* */ 45 | 46 | -------------------------------------------------------------------------------- /thirdparty/predicates/printing.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | /** 6 | * Print the bit representation of a double. 7 | */ 8 | void print_double(double number) 9 | { 10 | unsigned long long no; 11 | unsigned long long sign, expo; 12 | int exponent; 13 | int bottomi; 14 | 15 | no = *(unsigned long long *) &number; 16 | sign = no & 0x8000000000000000ll; 17 | expo = (no >> 52) & 0x7ffll; 18 | exponent = (int) expo; 19 | exponent = exponent - 1023; 20 | 21 | if (sign) 22 | printf("-"); 23 | else 24 | printf(" "); 25 | 26 | if (exponent == -1023) { 27 | printf("0.0000000000000000000000000000000000000000000000000000_ ( )"); 28 | } else { 29 | printf("1."); 30 | bottomi = -1; 31 | for (int i = 0; i < 52; i++) { 32 | if (no & 0x0008000000000000ll) { 33 | printf("1"); 34 | bottomi = i; 35 | } else { 36 | printf("0"); 37 | } 38 | no <<= 1; 39 | } 40 | printf("_%d (%d)", exponent, exponent - 1 - bottomi); 41 | } 42 | } 43 | 44 | 45 | /** 46 | * Print the bit representation of a float. 47 | */ 48 | void print_float(float number) 49 | { 50 | unsigned no; 51 | unsigned sign, expo; 52 | int exponent; 53 | int bottomi; 54 | 55 | no = *(unsigned *) &number; 56 | sign = no & 0x80000000; 57 | expo = (no >> 23) & 0xff; 58 | exponent = (int) expo; 59 | exponent = exponent - 127; 60 | if (sign) { 61 | printf("-"); 62 | } else { 63 | printf(" "); 64 | } 65 | if (exponent == -127) { 66 | printf("0.00000000000000000000000_ ( )"); 67 | } else { 68 | printf("1."); 69 | bottomi = -1; 70 | for (int i = 0; i < 23; i++) { 71 | if (no & 0x00400000) { 72 | printf("1"); 73 | bottomi = i; 74 | } else { 75 | printf("0"); 76 | } 77 | no <<= 1; 78 | } 79 | printf("_%3d (%3d)", exponent, exponent - 1 - bottomi); 80 | } 81 | } 82 | 83 | 84 | /** 85 | * Print the bit representation of an expansion. 86 | */ 87 | void print_expansion(size_t elen, double * e) 88 | { 89 | for (int i = elen - 1; i >= 0; i--) { 90 | print_double(e[i]); 91 | if (i > 0) 92 | printf(" +\n"); 93 | else 94 | printf("\n"); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /thirdparty/predicates/random.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | /*****************************************************************************/ 5 | /* */ 6 | /* doublerand() Generate a double with random 53-bit significand and a */ 7 | /* random exponent in [0, 511]. */ 8 | /* */ 9 | /*****************************************************************************/ 10 | 11 | double doublerand() 12 | { 13 | int a = rand(); 14 | int b = rand(); 15 | int c = rand(); 16 | double expo = 2; 17 | double result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); 18 | for (int i = 512; i <= 131072; i *= 2, expo = expo * expo) { 19 | if (c & i) { 20 | result *= expo; 21 | } 22 | } 23 | return result; 24 | } 25 | 26 | /*****************************************************************************/ 27 | /* */ 28 | /* narrowdoublerand() Generate a double with random 53-bit significand */ 29 | /* and a random exponent in [0, 7]. */ 30 | /* */ 31 | /*****************************************************************************/ 32 | 33 | double narrowdoublerand() 34 | { 35 | int a = rand(); 36 | int b = rand(); 37 | int c = rand(); 38 | double expo = 2; 39 | double result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); 40 | for (int i = 512; i <= 2048; i *= 2, expo = expo * expo) { 41 | if (c & i) { 42 | result *= expo; 43 | } 44 | } 45 | return result; 46 | } 47 | 48 | /*****************************************************************************/ 49 | /* */ 50 | /* uniformdoublerand() Generate a double with random 53-bit significand. */ 51 | /* */ 52 | /*****************************************************************************/ 53 | 54 | double uniformdoublerand() 55 | { 56 | int a = rand(); 57 | int b = rand(); 58 | double result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); 59 | return result; 60 | } 61 | 62 | /*****************************************************************************/ 63 | /* */ 64 | /* floatrand() Generate a float with random 24-bit significand and a */ 65 | /* random exponent in [0, 63]. */ 66 | /* */ 67 | /*****************************************************************************/ 68 | 69 | float floatrand() 70 | { 71 | int a = rand(); 72 | int c = rand(); 73 | float expo = 2; 74 | float result = (float) ((a - 1073741824) >> 6); 75 | for (int i = 512; i <= 16384; i *= 2, expo = expo * expo) { 76 | if (c & i) { 77 | result *= expo; 78 | } 79 | } 80 | return result; 81 | } 82 | 83 | /*****************************************************************************/ 84 | /* */ 85 | /* narrowfloatrand() Generate a float with random 24-bit significand and */ 86 | /* a random exponent in [0, 7]. */ 87 | /* */ 88 | /*****************************************************************************/ 89 | 90 | float narrowfloatrand() 91 | { 92 | int a = rand(); 93 | int c = rand(); 94 | float expo = 2; 95 | float result = (float) ((a - 1073741824) >> 6); 96 | for (int i = 512; i <= 2048; i *= 2, expo = expo * expo) { 97 | if (c & i) { 98 | result *= expo; 99 | } 100 | } 101 | return result; 102 | } 103 | 104 | /*****************************************************************************/ 105 | /* */ 106 | /* uniformfloatrand() Generate a float with random 24-bit significand. */ 107 | /* */ 108 | /*****************************************************************************/ 109 | 110 | float uniformfloatrand() 111 | { 112 | int a = rand(); 113 | float result = (float) ((a - 1073741824) >> 6); 114 | return result; 115 | } 116 | --------------------------------------------------------------------------------