From bfc4622467fa36f318865990b0cec567a75b7821 Mon Sep 17 00:00:00 2001 From: Drew Parsons Date: Mon, 5 Feb 2024 20:10:04 +0100 Subject: [PATCH] Import mshr_2019.2.0~git20230811.ff54a68+dfsg1.orig.tar.xz [dgit import orig mshr_2019.2.0~git20230811.ff54a68+dfsg1.orig.tar.xz] --- .gitignore | 4 + 3rdparty/CMakeLists.txt | 78 + 3rdparty/cgal-version.txt | 1 + 3rdparty/cgal.patch | 15 + CMakeLists.txt | 239 ++ COPYING | 674 ++++ ChangeLog.rst | 53 + README.rst | 81 + app/mshrable.cpp | 170 + cmake/FindTetGen.cmake | 42 + demo/python/classic.py | 39 + demo/python/csg-subdomains-2D.py | 45 + demo/python/deathstar.py | 45 + demo/python/extrude.py | 30 + demo/python/icecream.py | 48 + demo/python/materials.py | 41 + demo/python/propeller.py | 86 + demo/python/remove_degenerate.py | 18 + demo/python/simple-csg-3D.py | 47 + doc/Doxyfile | 1890 +++++++++++ doc/generate_rst.py | 312 ++ doc/sphinx/Makefile | 216 ++ doc/sphinx/README | 20 + doc/sphinx/source/ChangeLog.rst | 1 + doc/sphinx/source/conf.py | 342 ++ doc/sphinx/source/index.rst | 28 + fenics-dev-install.sh | 117 + include/mshr.h | 11 + include/mshr/ASCFileReader.h | 47 + include/mshr/CSGCGALDomain2D.h | 98 + include/mshr/CSGCGALDomain3D.h | 214 ++ include/mshr/CSGCGALMeshGenerator2D.h | 80 + include/mshr/CSGCGALMeshGenerator3D.h | 79 + include/mshr/CSGGeometries3D.h | 61 + include/mshr/CSGGeometry.h | 109 + include/mshr/CSGOperators.h | 325 ++ include/mshr/CSGPrimitive.h | 38 + include/mshr/CSGPrimitives2D.h | 191 ++ include/mshr/CSGPrimitives3D.h | 270 ++ include/mshr/DolfinMeshUtils.h | 66 + include/mshr/GlobalInitializer.h | 31 + include/mshr/MeshGenerator.h | 41 + include/mshr/Meshes.h | 27 + include/mshr/OFFFileReader.h | 47 + include/mshr/STLFileReader.h | 48 + include/mshr/SurfaceConsistency.h | 64 + include/mshr/SurfaceReconstruction.h | 46 + include/mshr/TetgenMeshGenerator3D.h | 61 + mshr-config.cmake.in | 17 + mshrConfig.cmake.in | 15 + python/CMakeLists.txt | 32 + python/cmake/Findmshr.cmake | 51 + python/config.json.in | 16 + python/mshr/__init__.py | 56 + python/setup.py | 43 + python/src/mshr.cpp | 214 ++ release.conf | 5 + src/ASCFileReader.cpp | 173 + src/CSGCGALDomain2D.cpp | 881 +++++ src/CSGCGALDomain3D.cpp | 2336 +++++++++++++ src/CSGCGALMeshGenerator2D.cpp | 408 +++ src/CSGCGALMeshGenerator3D.cpp | 382 +++ src/CSGGeometries3D.cpp | 262 ++ src/CSGGeometry.cpp | 137 + src/CSGOperators.cpp | 444 +++ src/CSGPrimitives2D.cpp | 285 ++ src/CSGPrimitives3D.cpp | 289 ++ src/DolfinMeshUtils.cpp | 298 ++ src/FuzzyPointLocator.h | 145 + src/GlobalInitializer.cpp | 43 + src/MeshGenerator.cpp | 112 + src/Meshes.cpp | 59 + src/OFFFileReader.cpp | 188 ++ src/Polygon_utils.h | 46 + ...lticomponent_mesh_domain_with_features_3.h | 163 + src/Polyhedron_utils.h | 2975 +++++++++++++++++ src/STLFileReader.cpp | 335 ++ src/SurfaceConsistency.cpp | 363 ++ src/SurfaceReconstruction.cpp | 370 ++ src/TetgenMeshGenerator3D.cpp | 248 ++ src/make_multicomponent_mesh_3.h | 65 + src/meshclean.h | 562 ++++ src/smoothing.h | 73 + src/triangulation_refinement.h | 318 ++ test/CMakeLists.txt | 120 + test/degenerate_removal.py | 14 + test/dummy.py | 5 + test/test-ASCFileReader.py | 23 + test/test-csg-predicates.py | 38 + test/test-csg-primitives-2d.py | 9 + test/test-csg.py | 39 + test/test-csggeometries.cpp | 30 + test/test-fuzzypointmap.py | 108 + test/test-mesh-generation.py | 10 + test/test-meshes.py | 6 + test/test-num-segments-2d.py | 13 + test/test.cpp | 142 + test/test2.cpp | 54 + test/test3D.cpp | 51 + use-mshr.cmake | 24 + 100 files changed, 19731 insertions(+) create mode 100644 .gitignore create mode 100644 3rdparty/CMakeLists.txt create mode 100644 3rdparty/cgal-version.txt create mode 100644 3rdparty/cgal.patch create mode 100644 CMakeLists.txt create mode 100644 COPYING create mode 100644 ChangeLog.rst create mode 100644 README.rst create mode 100644 app/mshrable.cpp create mode 100644 cmake/FindTetGen.cmake create mode 100644 demo/python/classic.py create mode 100644 demo/python/csg-subdomains-2D.py create mode 100644 demo/python/deathstar.py create mode 100644 demo/python/extrude.py create mode 100644 demo/python/icecream.py create mode 100644 demo/python/materials.py create mode 100644 demo/python/propeller.py create mode 100644 demo/python/remove_degenerate.py create mode 100644 demo/python/simple-csg-3D.py create mode 100644 doc/Doxyfile create mode 100644 doc/generate_rst.py create mode 100644 doc/sphinx/Makefile create mode 100644 doc/sphinx/README create mode 120000 doc/sphinx/source/ChangeLog.rst create mode 100644 doc/sphinx/source/conf.py create mode 100644 doc/sphinx/source/index.rst create mode 100755 fenics-dev-install.sh create mode 100644 include/mshr.h create mode 100755 include/mshr/ASCFileReader.h create mode 100644 include/mshr/CSGCGALDomain2D.h create mode 100644 include/mshr/CSGCGALDomain3D.h create mode 100644 include/mshr/CSGCGALMeshGenerator2D.h create mode 100644 include/mshr/CSGCGALMeshGenerator3D.h create mode 100644 include/mshr/CSGGeometries3D.h create mode 100644 include/mshr/CSGGeometry.h create mode 100644 include/mshr/CSGOperators.h create mode 100644 include/mshr/CSGPrimitive.h create mode 100644 include/mshr/CSGPrimitives2D.h create mode 100644 include/mshr/CSGPrimitives3D.h create mode 100644 include/mshr/DolfinMeshUtils.h create mode 100644 include/mshr/GlobalInitializer.h create mode 100644 include/mshr/MeshGenerator.h create mode 100644 include/mshr/Meshes.h create mode 100644 include/mshr/OFFFileReader.h create mode 100644 include/mshr/STLFileReader.h create mode 100644 include/mshr/SurfaceConsistency.h create mode 100644 include/mshr/SurfaceReconstruction.h create mode 100644 include/mshr/TetgenMeshGenerator3D.h create mode 100644 mshr-config.cmake.in create mode 100644 mshrConfig.cmake.in create mode 100644 python/CMakeLists.txt create mode 100644 python/cmake/Findmshr.cmake create mode 100644 python/config.json.in create mode 100644 python/mshr/__init__.py create mode 100644 python/setup.py create mode 100644 python/src/mshr.cpp create mode 100644 release.conf create mode 100755 src/ASCFileReader.cpp create mode 100644 src/CSGCGALDomain2D.cpp create mode 100644 src/CSGCGALDomain3D.cpp create mode 100644 src/CSGCGALMeshGenerator2D.cpp create mode 100644 src/CSGCGALMeshGenerator3D.cpp create mode 100644 src/CSGGeometries3D.cpp create mode 100644 src/CSGGeometry.cpp create mode 100644 src/CSGOperators.cpp create mode 100644 src/CSGPrimitives2D.cpp create mode 100644 src/CSGPrimitives3D.cpp create mode 100644 src/DolfinMeshUtils.cpp create mode 100644 src/FuzzyPointLocator.h create mode 100644 src/GlobalInitializer.cpp create mode 100644 src/MeshGenerator.cpp create mode 100644 src/Meshes.cpp create mode 100644 src/OFFFileReader.cpp create mode 100644 src/Polygon_utils.h create mode 100644 src/Polyhedral_multicomponent_mesh_domain_with_features_3.h create mode 100644 src/Polyhedron_utils.h create mode 100644 src/STLFileReader.cpp create mode 100644 src/SurfaceConsistency.cpp create mode 100644 src/SurfaceReconstruction.cpp create mode 100644 src/TetgenMeshGenerator3D.cpp create mode 100644 src/make_multicomponent_mesh_3.h create mode 100644 src/meshclean.h create mode 100644 src/smoothing.h create mode 100644 src/triangulation_refinement.h create mode 100644 test/CMakeLists.txt create mode 100644 test/degenerate_removal.py create mode 100644 test/dummy.py create mode 100644 test/test-ASCFileReader.py create mode 100644 test/test-csg-predicates.py create mode 100644 test/test-csg-primitives-2d.py create mode 100644 test/test-csg.py create mode 100644 test/test-csggeometries.cpp create mode 100644 test/test-fuzzypointmap.py create mode 100644 test/test-mesh-generation.py create mode 100644 test/test-meshes.py create mode 100644 test/test-num-segments-2d.py create mode 100644 test/test.cpp create mode 100644 test/test2.cpp create mode 100644 test/test3D.cpp create mode 100644 use-mshr.cmake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1720df4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/ +build.*/ +release/ +__pycache__ diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt new file mode 100644 index 0000000..97d1547 --- /dev/null +++ b/3rdparty/CMakeLists.txt @@ -0,0 +1,78 @@ +if (NOT USE_SYSTEM_CGAL) + # Enable ExternalProject CMake module + include(ExternalProject) + + # Set default ExternalProject root directory + set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/3rdparty) + + message(STATUS "Building CGAL") + + message(STATUS "Current source dir: ${CMAKE_CURRENT_SOURCE_DIR}") + message(STATUS "Binary dir: ${CMAKE_BINARY_DIR}") + + set(CGAL_INSTALL_DIR ${CMAKE_BINARY_DIR}/CGAL-installdir) + + if ( CMAKE_BUILD_TYPE ) + set(CGAL_BUILD_TYPE ${CMAKE_BUILD_TYPE}) + else() + set(CGAL_BUILD_TYPE Debug) + endif() + + # CGAL doesn't allow Developer, RelWithDebInfo and MinSizeRel as build type. + if ( "${CMAKE_BUILD_TYPE}" STREQUAL "Developer" ) + set(CGAL_BUILD_TYPE "Debug") + endif() + if ( "${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel" + OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" ) + set(CGAL_BUILD_TYPE "Release") + endif() + + message(STATUS "CGAL build type: ${CGAL_BUILD_TYPE}") + + # Note: Need to add fPIC in order to be able to link staticly with CGAL + # FIXME: The CMAKE_CXX_FLAGS=-std=c++11 is a hack to remove CGAL's dependency on boost.thread (see setup_Boost.cmake) + # This should be removed when CGAL's boost dependency is eventually removed. + ExternalProject_Add(CGAL + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/CGAL + INSTALL_DIR ${CGAL_INSTALL_DIR} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${CGAL_INSTALL_DIR} -DCMAKE_BUILD_TYPE=${CGAL_BUILD_TYPE} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_FLAGS=-std=c++11 -DCGAL_CXX_FLAGS=-fPIC -DWITH_CGAL_Core:BOOL=OFF -DWITH_CGAL_ImageIO:BOOL=OFF -DWITH_CGAL_Qt5:BOOL=OFF -DWITH_demos:BOOL=OFF -DWITH_examples:BOOL=OFF -DWITH_LEDA:BOOL=OFF -DWITH_GMP:BOOL=ON -DWITH_MPFR:BOOL=ON -DWITH_Eigen3:BOOL=ON -DBUILD_SHARED_LIBS:BOOL=OFF -DBOOST_ROOT=${BOOST_ROOT} -DGMP_INCLUDE_DIR=${GMP_INCLUDE_DIR} -DGMP_LIBRARIES=${GMP_LIBRARIES} -DMPFR_INCLUDE_DIR=${MPFR_INCLUDE_DIR} -DMPFR_LIBRARIES=${MPFR_LIBRARIES} -DEIGEN3_INCLUDE_DIR=${EIGEN3_INCLUDE_DIR} -DCGAL_HEADER_ONLY:BOOL=${CGAL_HEADER_ONLY} + + # -DWITH_ESBTL:BOOL=OFF \ + # -DWITH_GMPXX:BOOL=OFF \ + # -DWITH_IPE:BOOL=OFF \ + # -DWITH_LAPACK:BOOL=OFF \ + # -DWITH_LEDA:BOOL=OFF \ + # -DWITH_MPFI:BOOL=OFF \ + # -DWITH_NTL:BOOL=OFF \ + # -DWITH_OpenGL:BOOL=OFF \ + # -DWITH_OpenNL:BOOL=OFF \ + # -DWITH_QGLViewer:BOOL=OFF \ + # -DWITH_RS:BOOL=OFF \ + # -DWITH_RS3:BOOL=OFF \ + # -DWITH_TAUCS:BOOL=OFF \ + # -DWITH_ZLIB:BOOL=OFF \ + + UPDATE_COMMAND "" + ) + + set(CGAL_INCLUDE_DIR ${CGAL_INSTALL_DIR}/include PARENT_SCOPE) + set(CGAL_LIB_DIR ${CGAL_INSTALL_DIR}/lib PARENT_SCOPE) + + # LINK_DIRECTORIES( ${CGAL_INSTALL_DIR}/lib ) + + # ADD_LIBRARY(CGAL STATIC IMPORTED) + # SET_TARGET_PROPERTIES(TARGET cgal PROPERTY + # IMPORTED_LOCATION ${CGAL_INSTALL_DIR}/lib/libCGAL.a) + + # ADD_LIBRARY(CGAL_Core STATIC IMPORTED) + # SET_TARGET_PROPERTIES(TARGET cgal PROPERTY + # IMPORTED_LOCATION ${CGAL_INSTALL_DIR}/lib/libCGAL_Core.a) +endif() + +if (NOT USE_SYSTEM_TETGEN) + add_subdirectory( tetgen1.5.0 ) +endif() + +set(EXTERNAL_INCLUDE_DIRS "${CGAL_INSTALL_DIR}/include;${TETGEN_INCLUDE_DIR}" PARENT_SCOPE) +set(EXTERNAL_LIBS "tet" PARENT_SCOPE) +set(EXTERNAL_DEFINITIONS "${TETGEN_DEFINITIONS}" PARENT_SCOPE) diff --git a/3rdparty/cgal-version.txt b/3rdparty/cgal-version.txt new file mode 100644 index 0000000..f588584 --- /dev/null +++ b/3rdparty/cgal-version.txt @@ -0,0 +1 @@ +4.12 diff --git a/3rdparty/cgal.patch b/3rdparty/cgal.patch new file mode 100644 index 0000000..c004510 --- /dev/null +++ b/3rdparty/cgal.patch @@ -0,0 +1,15 @@ +diff --git a/3rdparty/CGAL/CMakeLists.txt b/3rdparty/CGAL/CMakeLists.txt +index d5173fd..0ae9026 100644 +--- a/3rdparty/CGAL/CMakeLists.txt ++++ b/3rdparty/CGAL/CMakeLists.txt +@@ -890,7 +890,9 @@ foreach (dir ${CGAL_CONFIGURED_PACKAGES}) + install(DIRECTORY ${dir}/include/CGAL DESTINATION ${CGAL_INSTALL_INC_DIR} PATTERN ".svn" EXCLUDE) + endif() + endforeach() +-install(DIRECTORY ${CMAKE_BINARY_DIR}/include/CGAL DESTINATION ${CGAL_INSTALL_INC_DIR} PATTERN ".svn" EXCLUDE) ++if(EXISTS ${CMAKE_BINARY_DIR}/include/CGAL) ++ install(DIRECTORY ${CMAKE_BINARY_DIR}/include/CGAL DESTINATION ${CGAL_INSTALL_INC_DIR} PATTERN ".svn" EXCLUDE) ++endif() + + file(GLOB scripts "scripts/*") + list(SORT scripts) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f1e50be --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,239 @@ +# Require CMake 3.5 +cmake_minimum_required(VERSION 3.5) + +project( MSHR ) +set(MSHR_VERSION_RELEASE 0) +set(MSHR_VERSION_MAJOR "2019") +set(MSHR_VERSION_MINOR "2") +set(MSHR_VERSION_MICRO "0") +set(MSHR_VERSION "${MSHR_VERSION_MAJOR}.${MSHR_VERSION_MINOR}.${MSHR_VERSION_MICRO}") +if (NOT MSHR_VERSION_RELEASE) + set(MSHR_VERSION "${MSHR_VERSION}.dev0") +endif() + +# CGAL setup +option(USE_SYSTEM_CGAL "Do not build CGAL, but use an existing build instead." OFF) +if (USE_SYSTEM_CGAL) + find_package(CGAL 5 CONFIG REQUIRED) +endif() + +# Borrow some cmake modules from cgal +if (USE_SYSTEM_CGAL) + set(CMAKE_MODULE_PATH "${CGAL_MODULES_DIR}") +else() + set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/3rdparty/CGAL/cmake/modules") +endif() + +# Add cmake directory to module path +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +option(USE_SYSTEM_TETGEN "Do not build tetgen, but use an existing build instead." OFF) +if (USE_SYSTEM_TETGEN) + find_package(TetGen) +endif() + +# Helper macro for testing if particular value is contained in list +# Taken from http://www.cmake.org/Wiki/CMakeMacroListOperations#LIST_CONTAINS +MACRO(LIST_CONTAINS var value) + SET(${var}) + FOREACH (value2 ${ARGN}) + IF (${value} STREQUAL ${value2}) + SET(${var} TRUE) + ENDIF (${value} STREQUAL ${value2}) + ENDFOREACH (value2) +ENDMACRO(LIST_CONTAINS) + +# Use C++14 to support std:enable_if used by CGAL 5 +set(CMAKE_CXX_STANDARD 14) + +# Boost +# This is workaround to avoid that find_package(Boost) +# picks up th ewrong boost when a hint is given +set(BOOST_ROOT $ENV{BOOST_DIR} $ENV{BOOST_HOME}) +if (BOOST_ROOT) + set(Boost_NO_SYSTEM_PATHS on) +endif() + +# Prevent FindBoost.cmake from looking for system Boost{foo}.cmake files +set(Boost_NO_BOOST_CMAKE true) +find_package( Boost REQUIRED system filesystem program_options ) +# Save this because it will be overwritten by find_package(DOLFIN) +set(Boost_MSHR_LIBRARIES ${Boost_LIBRARIES}) +include_directories("${Boost_INCLUDE_DIRS}") + +# GMP_INCLUDE_DIR - the GMP include directory +# GMP_LIBRARIES_DIR - directory where the GMP libraries are located +# GMP_LIBRARIES - Link these to use GMP +find_package(GMP REQUIRED) +include_directories("${GMP_INCLUDE_DIR}") + +# Try to find the MPFR libraries +# MPFR_FOUND - system has MPFR lib +# MPFR_INCLUDE_DIR - the MPFR include directory +# MPFR_LIBRARIES_DIR - Directory where the MPFR libraries are located +# MPFR_LIBRARIES - the MPFR libraries +# MPFR_IN_CGAL_AUXILIARY - TRUE if the MPFR found is the one distributed with CGAL in the auxiliary folder +find_package(MPFR REQUIRED) +include_directories("${MPFR_INCLUDE_DIR}") + +find_package(Eigen3 REQUIRED) +include_directories("${EIGEN3_INCLUDE_DIR}") + +# use CGAL in header-only mode +option (CGAL_HEADER_ONLY "Use CGAL in header-only mode" ON) +if (CGAL_HEADER_ONLY) + add_definitions( -DCGAL_HEADER_ONLY ) +endif() + +add_subdirectory(3rdparty) +#include_directories(BEFORE ${CGAL_INCLUDE_DIR}) +include_directories(BEFORE ${EXTERNAL_INCLUDE_DIRS}) +add_definitions("${EXTERNAL_DEFINITIONS}") + +find_package(DOLFIN REQUIRED) +include(${DOLFIN_USE_FILE}) + +# Set installation directories +set(INSTALL_LIB_DIR lib CACHE PATH "Installation directory for libraries") +set(INSTALL_BIN_DIR bin CACHE PATH "Installation directory for executables") +set(INSTALL_INCLUDE_DIR include CACHE PATH "Installation directory for header files") +set(SHARE_DIR "share/mshr" CACHE PATH "Shared data installation directory.") + +# Make relative paths absolute (needed later on) +foreach(p LIB BIN INCLUDE CMAKE) + set(var INSTALL_${p}_DIR) + if(NOT IS_ABSOLUTE "${${var}}") + set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}") + endif() +endforeach() + + +# include for local header directory +include_directories( BEFORE include ) + +file( GLOB_RECURSE SOURCES src/*.cpp ) + +add_library(mshr SHARED ${SOURCES}) +if (NOT USE_SYSTEM_CGAL) + add_dependencies( mshr CGAL ) +endif() + +# Append the library version information to the library target properties +option(MSHR_WITH_LIBRARY_VERSION "Build with library version information." ON) +if (MSHR_WITH_LIBRARY_VERSION) + string(REPLACE "+" "" MSHR_LIBRARY_VERSION ${MSHR_VERSION}) + # This setting of SOVERSION assumes that any API change + # will increment either the minor or major version number. + set(MSHR_LIBRARY_PROPERTIES ${MSHR_LIBRARY_PROPERTIES} + VERSION ${MSHR_LIBRARY_VERSION} + SOVERSION ${MSHR_VERSION_MAJOR}.${MSHR_VERSION_MINOR} + ) +endif() +set_target_properties(mshr PROPERTIES ${MSHR_LIBRARY_PROPERTIES}) + +# Link the executable to (static) CGAL libraries +target_link_libraries( mshr ${GMP_LIBRARIES} + ${MPFR_LIBRARIES} + tet + ) + +# Fix CGAL's random generator (usefull to reproduce bugs) +option(INIT_CGAL_RANDOM "Use fixed seed for CGAL's pseudo random generator" OFF) +if (INIT_CGAL_RANDOM) + add_definitions( -DINIT_RANDOM_GENERATOR=0 ) +endif() + +# +option(ENABLE_EXPERIMENTAL "Enable experimental code" OFF) +if(ENABLE_EXPERIMENTAL) + message(STATUS "Enabling experimental code") + add_definitions( -DMSHR_ENABLE_EXPERIMENTAL ) +endif() + + +export(PACKAGE mshr) + +target_link_libraries( mshr + dolfin + ${Boost_MSHR_LIBRARIES} + ) + +# CMAKE_CXX_STANDARD is ignored ;( Need to set standard manually here. +set_property(TARGET mshr PROPERTY CXX_STANDARD 14) + +# install library +install(TARGETS mshr + RUNTIME DESTINATION ${INSTALL_BIN_DIR} + LIBRARY DESTINATION ${INSTALL_LIB_DIR} + ARCHIVE DESTINATION ${INSTALL_LIB_DIR} + ) + +option(ENABLE_MSHRABLE "Enable building the command line client, mshrable" OFF) +if (ENABLE_MSHRABLE) + add_executable( mshrable app/mshrable.cpp ) + + target_link_libraries( mshrable + mshr + dolfin + ${Boost_MSHR_LIBRARIES} + ) + +# CMAKE_CXX_STANDARD is ignored ;( Need to set standard manually here. +set_property(TARGET mshrable PROPERTY CXX_STANDARD 14) + + # install app + install(TARGETS mshrable + RUNTIME DESTINATION ${INSTALL_BIN_DIR} + LIBRARY DESTINATION ${INSTALL_LIB_DIR} + ARCHIVE DESTINATION ${INSTALL_LIB_DIR} + ) +else() + message(STATUS "Disabling building command line client mshrable") +endif() + +# install header files +install(DIRECTORY include/ DESTINATION ${INSTALL_INCLUDE_DIR}) + +file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}" "${INSTALL_INCLUDE_DIR}") + +### add cmake configuration file ### + +# common for both config files +set(CONF_EXTERNAL_INCLUDE_DIRS "${DOLFIN_INCLUDE_DIRS};${DOLFIN_3RD_PARTY_INCLUDE_DIRS}") +set(CONF_EXTERNAL_LIBRARIES "${DOLFIN_LIBRARIES};${DOLFIN_3RD_PARTY_LIBRARIES}") +set(CONF_CXX_DEFINITIONS "${DOLFIN_CXX_DEFINITIONS}") +string(REPLACE "\"" "\\\"" CONF_CXX_DEFINITIONS "${CONF_CXX_DEFINITIONS}") +set(CONF_CXX_FLAGS "${DOLFIN_CXX_FLAGS}") + +# for the build tree +set(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/include") +set(CONF_LIBRARIES_DIRS "${PROJECT_BINARY_DIR}") + +configure_file(mshrConfig.cmake.in "${PROJECT_BINARY_DIR}/mshrConfig.cmake" @ONLY) +configure_file(mshr-config.cmake.in "${PROJECT_BINARY_DIR}/mshr-config.cmake" @ONLY) + +# ... and for the install tree +set(CONF_INCLUDE_DIRS "${INSTALL_INCLUDE_DIR}") +set(CONF_LIBRARIES_DIRS "${INSTALL_LIB_DIR}") +configure_file(mshrConfig.cmake.in + "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/mshrConfig.cmake" @ONLY) +configure_file(mshr-config.cmake.in + "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/mshr-config.cmake" @ONLY) + +# Install the FooBarConfig.cmake and FooBarConfigVersion.cmake +install(FILES "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/mshrConfig.cmake" DESTINATION "${INSTALL_LIB_DIR}/cmake/mshr" COMPONENT dev) +install(FILES "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/mshr-config.cmake" DESTINATION "${INSTALL_LIB_DIR}/cmake/mshr" COMPONENT dev) +#install(FILES "Use-mshr.cmake" DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev) + + + + + +option(ENABLE_TESTS "Enable testing" OFF) + +if(ENABLE_TESTS) + enable_testing() + add_subdirectory(test) +else() + message(STATUS "Testing disabled") +endif() diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/ChangeLog.rst b/ChangeLog.rst new file mode 100644 index 0000000..8418d2d --- /dev/null +++ b/ChangeLog.rst @@ -0,0 +1,53 @@ +Changelog +========= + +2019.2.0.dev0 +------------- + +- No changes yet. + +2019.1.0 (2019-04-17) +--------------------- + +- No changes. + +2018.1.0 (2018-06-14) +-------------- + +- Drop python2 support. +- Switch to pybind11 bindings from SWIG. + +2017.2.0 (2017-12-05) +--------------------- + +- Nothing changed yet + +2017.1.0 (2017-05-11) +--------------------- + +- Bugfixes + +2016.2.0 (2016-11-30) +--------------------- + +- Improvements and bugfixes +- Upgrade to CGAL 4.9 and use CGAL as header only library + +2016.1.0 (2016-06-23) +--------------------- + +- Major improvements, in particular boundary conditions + +1.6.0 (2015-07-28) +------------------ + +- Upgrade to CGAL 4.6 +- Add more demos +- Faster and more memory efficient implementation of csg operations + with -DENABLE_EXPERIMENTAL=True +- Bugfixes and cleanups + +1.5.0 (2015-02-04) +------------------ + +- Initial release of mshr. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..b9b9be4 --- /dev/null +++ b/README.rst @@ -0,0 +1,81 @@ +==== +mshr +==== + +mshr is the mesh generation component of `FEniCS +`_. It generates simplicial `DOLFIN +`_ meshes in 2D and 3D +from geometries described by Constructive Solid Geometry (CSG) or from +surface files, utilizing CGAL and Tetgen as mesh generation backends. + +Authors: + | Benjamin Kehlet + +Contributors: + | Anders Logg + | Johannes Ring + | Garth N. Wells + +Documentation +============= + +The documentation is currently being prepared here: +`https://bitbucket.org/fenics-project/mshr/wiki `_ + +Installation +============ +For Debian and Ubuntu users, installing mshr is as easy as:: + + sudo apt-get install fenics + +To get a recent version of, enable the FEniCS PPA first. See `Installation instructions for Ubuntu `_ + +To build mshr from source, run:: + + cmake + make + make install + +mshr's build script will also build CGAL and Tetgen from source and +include them in the binary. + +Dependencies +============ + +mshr needs `DOLFIN `_ +with Python support (pyDolfin). `CGAL `_ and +`Tetgen `_ are shipped with mshr and built from +source automatically. CGAL needs `Gnu GMP `_ and +`Gnu MPFR `_. + +License +======= + +mshr is licensed under GPL version 3 or (at your option) any later +version. + +Contact +======= + +mshr is hosted at https://bitbucket.org/fenics-project/mshr/ + +For comments and requests, send an email to the FEniCS mailing list:: + + fenics@fenicsproject.org + +For bug reports and feature requests, visit mshr's issue tracker at BitBucket:: + + https://bitbucket.org/fenics-project/mshr/issues + +Contributions +============= + +Contributions are welcome! + +Please read about contributing to FEniCS here: +http://fenicsproject.org/contributing/ + +If you plan to implement a new feature, please discuss it at the +FEniCS mailing list beforehand. Smaller patches and bugfixes are +easiest submitted as `pull request on Bitbucket +`_. \ No newline at end of file diff --git a/app/mshrable.cpp b/app/mshrable.cpp new file mode 100644 index 0000000..1f90a9e --- /dev/null +++ b/app/mshrable.cpp @@ -0,0 +1,170 @@ +// Copyright (C) 2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace po = boost::program_options; + +// This program reads in a surface from file and generates mesh + + +namespace +{ +void print_mesh_statistics(const dolfin::Mesh& m) +{ + std::cout << "Dolfin mesh of topological dimension " << m.topology().dim() << std::endl; + std::cout << " " << m.num_vertices() << " vertices" << std::endl; + std::cout << " " << m.num_cells() << " cells" << std::endl; + + const std::pair volume_min_max = mshr::DolfinMeshUtils::cell_volume_min_max(m); + std::cout << "Min cell volume: " << volume_min_max.first << std::endl; + std::cout << "Max cell volume: " << volume_min_max.second << std::endl; + const std::pair radii_ratio = dolfin::MeshQuality::radius_ratio_min_max(m); + std::cout << "Minimum cell radii ratio: " << radii_ratio.first << std::endl; + std::cout << "Maximum cell radii ratio: " << radii_ratio.second << std::endl; +} +//----------------------------------------------------------------------------- +// Define options and parse command line +void handle_commandline(int argc, char** argv, po::variables_map &vm) +{ + // Command line options + po::options_description visible("Generate mesh from surface file"); + visible.add_options() + ("outfile,o", po::value(), "Filename of generated Dolfin mesh") + ("resolution,r", po::value()->default_value(15.0), "Resolution of result mesh") + ("stats,s", "Write some statistics of the mesh to stdout") + ("polyout", po::value(), "Write the polyhedron to a .off file (and do not create a mesh)") + ("polystats", "Write statistics of polyhedron (and do not create a mesh") + ("backend,b", po::value()->default_value("cgal"), "Use 3D mesh generation backend [tetgen|cgal]") + ("degenerate_tolerance", po::value()->default_value(1e-12), "Tolerance for considering a facet as degenerate. Set to 0 to not remove degenerate facets") + ("check-mesh", "Check consistency of output mesh (most for debugging/testing") + ("verbose,v", "Output more information about what is going on") + ("help,h", "write help message"); + + // Options not shown to the user + po::options_description hidden("Hidden options"); + hidden.add_options() + ("input-file", po::value(), "Input file") + ; + + po::options_description cmdline("Generate mesh from surface file"); + cmdline.add(visible).add(hidden); + + //Command line positional arguments + po::positional_options_description p; + p.add("input-file", 1); + + // parse command line + po::store(po::command_line_parser(argc, argv) + .options(cmdline) + .positional(p).run(), vm); + + // Print help message if requested + // (before notify to avoid error messages if only --help is given) + if (vm.count("help")) + { + std::cout << visible << std::endl; + exit(EXIT_SUCCESS); + } + + po::notify(vm); + +} +} //end anonymous namespace +//----------------------------------------------------------------------------- +int main(int argc, char** argv) +{ + // Ensure Dolfin initializes MPI correctly. (may be a noop, dependent on dolfin compile options) + dolfin::SubSystemsManager::init_mpi(); + + po::variables_map vm; + handle_commandline(argc, argv, vm); + + if (vm.count("verbose")) + dolfin::set_log_level(dolfin::TRACE); + + // Read the infile + if (!boost::filesystem::exists(vm["input-file"].as())) + { + std::cerr << "File " << vm["input-file"].as() << "does not exist" << std::endl; + exit(1); + } + + + std::shared_ptr surf(new mshr::Surface3D(vm["input-file"].as())); + surf->degenerate_tolerance = vm["degenerate_tolerance"].as(); + + // Operations that disable mesh generation + if (vm.count("polyout") || vm.count("polystats")) + { + mshr::CSGCGALDomain3D domain(surf); + + if (vm.count("polyout")) + { + std::string extension = boost::filesystem::extension(vm["polyout"].as()); + + if (extension != ".off") + { + std::cerr << "Unknown file type: " << extension << std::endl; + exit(1); + } + + domain.save_off(vm["polyout"].as()); + } + + if (vm.count("polystats")) + std::cout << domain.str(true) << std::endl; + + exit(EXIT_SUCCESS); + } + + // Generate the mesh + std::shared_ptr m = mshr::generate_mesh(surf, + vm["resolution"].as(), + vm["backend"].as()); + + // Output mesh if requested + if (vm.count("outfile")) + { + dolfin::File f(vm["outfile"].as()); + f << *m; + } + + if (vm.count("stats")) + print_mesh_statistics(*m); + + if (vm.count("check-mesh")) + { + if (!mshr::DolfinMeshUtils::check_mesh(*m)) + { + std::cout << " Error: Mesh check failed" << std::endl; + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/cmake/FindTetGen.cmake b/cmake/FindTetGen.cmake new file mode 100644 index 0000000..db5e6e6 --- /dev/null +++ b/cmake/FindTetGen.cmake @@ -0,0 +1,42 @@ +#.rst: +# FindTetGen +# -------- +# +# Find TetGen library +# +# Find the TetGen includes and library This module defines +# +# :: +# +# TETGEN_INCLUDE_DIRS, where to find triangle.h. +# TETGEN_LIBRARIES, libraries to link against to use triangle. +# TETGEN_FOUND, If false, do not try to use TETGEN. +# +# +# +# +# +#============================================================================= + +find_path(TETGEN_INCLUDE_DIR tetgen.h + DOC "The TetGen include directory") + +set(TETGEN_NAMES ${TETGEN_NAMES} libtetgen tetgen tet) +find_library(TETGEN_LIBRARY NAMES ${TETGEN_NAMES} + DOC "The TetGen library") + +# handle the QUIETLY and REQUIRED arguments and set TETGEN_FOUND to TRUE if +# all listed variables are TRUE +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(TETGEN + REQUIRED_VARS TETGEN_LIBRARY + TETGEN_INCLUDE_DIR + VERSION_VAR TETGEN_VERSION_STRING) + +if(TETGEN_FOUND) + set( TETGEN_LIBRARIES ${TETGEN_LIBRARY} ) + set( TETGEN_INCLUDE_DIRS ${TETGEN_INCLUDE_DIR} ) + add_definitions(-DTETLIBRARY) +endif() + +mark_as_advanced(TETGEN_INCLUDE_DIR TETGEN_LIBRARY) diff --git a/demo/python/classic.py b/demo/python/classic.py new file mode 100644 index 0000000..2419563 --- /dev/null +++ b/demo/python/classic.py @@ -0,0 +1,39 @@ +# Copyright (C) 2015 Anders Logg +# +# This file is part of mshr. +# +# mshr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# mshr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with mshr. If not, see . + +from dolfin import * +from mshr import * + +# Create geometry +s1 = Sphere(Point(0, 0, 0), 1.4) +b1 = Box(Point(-1, -1, -1), Point(1, 1, 1)) +c1 = Cylinder(Point(-2, 0, 0), Point(2, 0, 0), 0.8, 0.8) +c2 = Cylinder(Point(0, -2, 0), Point(0, 2, 0), 0.8, 0.8) +c3 = Cylinder(Point(0, 0, -2), Point(0, 0, 2), 0.8, 0.8) + +geometry = s1*b1 - (c1 + c2 + c3) + +# Create mesh +mesh = generate_mesh(geometry, 64) + +# Save to file and plot +File("classic.pvd") << mesh + +plot(mesh) + +# import matplotlib.pyplot as plt +# plt.show() diff --git a/demo/python/csg-subdomains-2D.py b/demo/python/csg-subdomains-2D.py new file mode 100644 index 0000000..636f412 --- /dev/null +++ b/demo/python/csg-subdomains-2D.py @@ -0,0 +1,45 @@ +# Copyright (C) 2012-2014 Benjamin Kehlet +# +# This file is part of mshr. +# +# mshr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# mshr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with mshr. If not, see . + +import dolfin +from mshr import * + +#dolfin.set_log_level(dolfin.TRACE) + +# Define 2D geometry +domain = Rectangle(dolfin.Point(0., 0.), dolfin.Point(5., 5.)) \ + - Rectangle(dolfin.Point(2., 1.25), dolfin.Point(3., 1.75)) \ + - Circle(dolfin.Point(1, 4), .25) \ + - Circle(dolfin.Point(4, 4), .25) +domain.set_subdomain(1, Rectangle(dolfin.Point(1., 1.), dolfin.Point(4., 3.))) +domain.set_subdomain(2, Rectangle(dolfin.Point(2., 2.), dolfin.Point(3., 4.))) + +print("Verbose output of 2D geometry:") +dolfin.info(domain, True) + +# Generate and plot mesh +mesh2d = generate_mesh(domain, 45) +print(mesh2d) + +dolfin.plot(mesh2d, "2D mesh") + +# Convert subdomains to mesh function for plotting +mf = dolfin.MeshFunction("size_t", mesh2d, 2, mesh2d.domains()) +dolfin.plot(mf, "Subdomains") + +# import matplotlib.pyplot as plt +# plt.show() diff --git a/demo/python/deathstar.py b/demo/python/deathstar.py new file mode 100644 index 0000000..dbbf520 --- /dev/null +++ b/demo/python/deathstar.py @@ -0,0 +1,45 @@ +# Copyright (C) 2015 Anders Logg +# +# This file is part of mshr. +# +# mshr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# mshr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with mshr. If not, see . + +from dolfin import * +from mshr import * +from math import pi, sin, cos, sqrt + +# Parameters +R = 1.02 +r = 0.4 +t = 10 +x = R*cos(float(t) / 180 * pi) +y = 0 +z = R*sin(t) + +# Create geometry +s1 = Sphere(Point(0, 0, 0), 1) +s2 = Sphere(Point(x, y, z), r) +b1 = Box(Point(-2, -2, -0.03), Point(2, 2, 0.03)) +geometry = s1 - s2 - b1 + +# Create mesh +mesh = generate_mesh(geometry, 32) + +# Save to file and plot +File("deathstar.pvd") << mesh + +plot(mesh) + +# import matplotlib.pyplot as plt +# plt.show() diff --git a/demo/python/extrude.py b/demo/python/extrude.py new file mode 100644 index 0000000..f63d69f --- /dev/null +++ b/demo/python/extrude.py @@ -0,0 +1,30 @@ +# Copyright (C) 2015 Benjamin Kehlet +# +# This file is part of mshr. +# +# mshr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# mshr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with mshr. If not, see . + +# This demo illustrates how a 2D geometry can be extruded to 3D. + +from dolfin import * +import mshr + +g2d = mshr.Circle(Point(0,0), 1.2) + mshr.Circle(Point(0, 1.2), 1.2) + +# Any simple 2D geometry can be extruded to 3D +g3d = mshr.Extrude2D(g2d, + .2) # The z "thickness" + +m = mshr.generate_mesh(g3d, 15) +plot(m) diff --git a/demo/python/icecream.py b/demo/python/icecream.py new file mode 100644 index 0000000..b9031a2 --- /dev/null +++ b/demo/python/icecream.py @@ -0,0 +1,48 @@ +# Copyright (C) 2014 Benjamin Kehlet +# +# This file is part of mshr. +# +# mshr is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# mshr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mshr. If not, see . +# + +import dolfin +from mshr import * + +# Define 3D geometry +sphere = Sphere(dolfin.Point(0, 0, 0), 0.5) +cone = Cylinder(dolfin.Point(0, 0, 0), dolfin.Point(0, 0, -1), .35, .1) + +geometry = cone + sphere + +# Geometry surfaces can be saved to off files +# which can be viewed by eg. MeshLab +meshing_domain = CSGCGALDomain3D(geometry) +meshing_domain.remove_degenerate_facets(1e-12) +meshing_domain.save("icecream.off") + +# Test printing +dolfin.info("\nCompact output of 3D geometry:") +dolfin.info(geometry) +dolfin.info("\nVerbose output of 3D geometry:") +dolfin.info(geometry, True) + +# Generate and plot mesh +m = generate_mesh(geometry, 16, "cgal") +print(m) + +dolfin.plot(m, "3D mesh") + +# import matplotlib.pyplot as plt +# plt.show() + diff --git a/demo/python/materials.py b/demo/python/materials.py new file mode 100644 index 0000000..1a64718 --- /dev/null +++ b/demo/python/materials.py @@ -0,0 +1,41 @@ +# Copyright (C) 2014 Benjamin Kehlet +# +# +# This file is part of mshr. +# +# mshr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# mshr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with mshr. If not, see . + +import dolfin +from mshr import * + +#dolfin.set_log_level(dolfin.TRACE) + +# Define 2D geometry +domain = Rectangle(dolfin.Point(0., 0.), dolfin.Point(1., 1.)) - Circle(dolfin.Point(0.0, 0.0), .35) +domain.set_subdomain(1, Rectangle(dolfin.Point(.05, .05), dolfin.Point(.95, .95))) +domain.set_subdomain(2, Circle(dolfin.Point(0, 0), .45)) +domain.set_subdomain(3, Circle(dolfin.Point(0,0), .6)) + +# Generate and plot mesh +mesh2d = generate_mesh(domain, 45) +dolfin.plot(mesh2d, "2D mesh") + +# Convert subdomains to mesh function for plotting +mf = dolfin.MeshFunction("size_t", mesh2d, 2, mesh2d.domains()) + +dolfin.plot(mf, "Subdomains") + +# import matplotlib.pyplot as plt +# plt.show() + diff --git a/demo/python/propeller.py b/demo/python/propeller.py new file mode 100644 index 0000000..6dd34c7 --- /dev/null +++ b/demo/python/propeller.py @@ -0,0 +1,86 @@ +# Copyright (C) 2014 Anders Logg +# +# This file is part of mshr. +# +# mshr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# mshr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with mshr. If not, see . + +from __future__ import print_function +from mshr import * +from dolfin import * + +# Set parameters +r = 0.125 +R = 0.5 +w = 0.3 +h = 0.025 +rotate_blades = True +include_tip = False +extra_rotation = True # only applied to inner mesh +resolution = 4 + +# Define geometries +sphere = Sphere(Point(0, 0, 0), 2*R) +geometry_inside = CSGGeometries.propeller(r, R, w, h, rotate_blades, include_tip) +geometry_outside = sphere - geometry_inside + +# Generate meshes +mesh_inside = generate_mesh(geometry_inside, resolution) +mesh_outside = generate_mesh(geometry_outside, resolution) + +# Rotate blades +if extra_rotation: + print("Rotating blades...") + c = mesh_inside.coordinates() + for i, (x, y, z) in enumerate(c): + + # Compute distance to axis + _r = sqrt(x**2 + y**2) + + # Compute rotation angle + v = -2*max(0, _r - r) + + # Rotate blades + xx = x; yy = y; zz = z; + if x > 0 and abs(y) < 5*h: + yy = cos(v)*y - sin(v)*z + zz = sin(v)*y + cos(v)*z + elif x < 0 and abs(y) < 5*h: + yy = cos(v)*y + sin(v)*z + zz = -sin(v)*y + cos(v)*z + elif y > 0 and abs(x) < 5*h: + xx = cos(v)*x + sin(v)*z + zz = -sin(v)*x + cos(v)*z + elif y < 0 and abs(x) < 5*h: + xx = cos(v)*x - sin(v)*z + zz = sin(v)*x + cos(v)*z + + # Store coordinates + c[i][0] = xx + c[i][1] = yy + c[i][2] = zz + +# Report size of meshes +print("Mesh of propeller inside: %d cells" % mesh_inside.num_cells()) +print("Mesh of propeller outside: %d cells" % mesh_outside.num_cells()) + +# Save meshes to file +File("propeller_inside.xml.gz") << mesh_inside +File("propeller_outside.xml.gz") << mesh_outside + +# Plot mesh +plot(mesh_inside) +plot(mesh_outside) + +# import matplotlib.pyplot as plt +# plt.show() diff --git a/demo/python/remove_degenerate.py b/demo/python/remove_degenerate.py new file mode 100644 index 0000000..76b4d81 --- /dev/null +++ b/demo/python/remove_degenerate.py @@ -0,0 +1,18 @@ +from __future__ import print_function +import mshr +import dolfin + +TOLERANCE = 1e-10 + +# This geometry generates a lot of degenerate facets +cone = mshr.Cylinder(dolfin.Point(-1.0, 1.0, 1.0), dolfin.Point(1.0, -1.0, -1.0), .5, .5) +cyl = mshr.Cone(dolfin.Point(1.0, -1.0, 1.0), dolfin.Point(-1.0, 1.0, -1.0), .5) +geometry = cone + cyl; + +polyhedral_domain = mshr.CSGCGALDomain3D(geometry) + +print("Degenerate facets after boolean operation: {0}".format(polyhedral_domain.num_degenerate_facets(TOLERANCE))) +polyhedral_domain.remove_degenerate_facets(TOLERANCE) + +dolfin.info(polyhedral_domain, True) + diff --git a/demo/python/simple-csg-3D.py b/demo/python/simple-csg-3D.py new file mode 100644 index 0000000..62b2a89 --- /dev/null +++ b/demo/python/simple-csg-3D.py @@ -0,0 +1,47 @@ +# Copyright (C) 2012-2014 Benjamin Kehlet +# +# This file is part of mshr. +# +# mshr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# mshr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with mshr. If not, see . + +import dolfin +from mshr import * + +# Define 3D geometry +box = Box(dolfin.Point(0, 0, 0), dolfin.Point(1, 1, 1)) +sphere = Sphere(dolfin.Point(0, 0, 0), 0.3) +cylinder = Cylinder(dolfin.Point(0, 0, -1), dolfin.Point(0, 0, 1), 1., .5) + +domain = box + cylinder - sphere + +# Test printing +dolfin.info("\nCompact output of 3D geometry:") +dolfin.info(domain) +dolfin.info("\nVerbose output of 3D geometry:") +dolfin.info(domain, True) + +# Creating a mesh generator object gives access to parameters of the +# meshing backend +generator = CSGCGALMeshGenerator3D() +generator.parameters["edge_size"] = 0.025 +generator.parameters["facet_angle"] = 25.0 +generator.parameters["facet_size"] = 0.05 + +# Invoke the mesh generator +mesh = generator.generate(CSGCGALDomain3D(domain)) + +dolfin.plot(mesh, "3D mesh") + +# import matplotlib.pyplot as plt +# plt.show() diff --git a/doc/Doxyfile b/doc/Doxyfile new file mode 100644 index 0000000..b2bd620 --- /dev/null +++ b/doc/Doxyfile @@ -0,0 +1,1890 @@ +# Doxyfile 1.8.4 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed +# in front of the TAG it is preceding . +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = "mshr" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 1.4 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "Mesh generation in FEniCS" + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Latvian, Lithuanian, Norwegian, Macedonian, +# Persian, Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, +# Slovak, Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. Note that you specify absolute paths here, but also +# relative paths, which will be relative from the directory where doxygen is +# started. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, +# and language is one of the parsers supported by doxygen: IDL, Java, +# Javascript, CSharp, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, +# C++. For instance to make doxygen treat .inc files as Fortran files (default +# is PHP), and .f files as C (default is Fortran), use: inc=Fortran f=C. Note +# that for custom extensions you also need to set FILE_PATTERNS otherwise the +# files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES (the +# default) will make doxygen replace the get and set methods by a property in +# the documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields or simple typedef fields will be shown +# inline in the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO (the default), structs, classes, and unions are shown on a separate +# page (for HTML and Man pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can +# be an expensive process and often the same symbol appear multiple times in +# the code, doxygen keeps a cache of pre-resolved symbols. If the cache is too +# small doxygen will become slower. If the cache is too large, memory is wasted. +# The cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid +# range is 0..9, the default is 0, corresponding to a cache size of 2^16 = 65536 +# symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if section-label ... \endif +# and \cond section-label ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. Do not use +# file names with spaces, bibtex cannot handle them. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ../include/mshr + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be ignored. +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MD_FILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = NO + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If left blank doxygen will +# generate a default style sheet. Note that it is recommended to use +# HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this +# tag will in the future become obsolete. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional +# user-defined cascading style sheet that is included after the standard +# style sheets created by doxygen. Using this option one can overrule +# certain style aspects. This is preferred over using HTML_STYLESHEET +# since it does not replace the standard style sheet and is therefor more +# robust against future updates. Doxygen will copy the style sheet file to +# the output directory. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely +# identify the documentation publisher. This should be a reverse domain-name +# style string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. Supported types are HTML-CSS, NativeMML (i.e. MathML) and +# SVG. The default value is HTML-CSS, which is slower, but has the best +# compatibility. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript +# pieces of code that will be used on startup of the MathJax code. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. +# There are two flavours of web server based search depending on the +# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for +# searching and an index file used by the script. When EXTERNAL_SEARCH is +# enabled the indexing and searching needs to be provided by external tools. +# See the manual for details. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain +# the search results. Doxygen ships with an example indexer (doxyindexer) and +# search engine (doxysearch.cgi) which are based on the open source search +# engine library Xapian. See the manual for configuration details. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will returned the search results when EXTERNAL_SEARCH is enabled. +# Doxygen ships with an example search engine (doxysearch) which is based on +# the open source search engine library Xapian. See the manual for configuration +# details. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH AND EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id +# of to a relative location where the documentation can be found. +# The format is: EXTRA_SEARCH_MAPPINGS = id1=loc1 id2=loc2 ... + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4 will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images +# or other source files which should be copied to the LaTeX output directory. +# Note that the files will be copied as-is; there are no commands or markers +# available. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = YES + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES Doxygen will generate DOCBOOK files +# that can be used to generate PDF. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the DOCBOOK pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. If left blank docbook will be used as the default path. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed +# in the related pages index. If set to NO, only the current project's +# pages will be listed. + +EXTERNAL_PAGES = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# manageable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/doc/generate_rst.py b/doc/generate_rst.py new file mode 100644 index 0000000..d9c55c6 --- /dev/null +++ b/doc/generate_rst.py @@ -0,0 +1,312 @@ +# This script generates a set of .md (MarkDown) files +# from xml files generated by doxygen. + +# The script does two sweeps. +# First all xml are parsed and stored in dictionaries +# with their id as key. +# The second sweep generates one page (a markdown file) pr class +# and collects the information for main page which is generated +# at the end. + +import sys, os +from xml.etree import ElementTree +import subprocess + +input_dir = os.path.abspath(sys.argv[1]) +output_dir = os.path.abspath(sys.argv[2]) + +if not os.path.exists(output_dir) : + os.mkdir(output_dir) + +if not os.path.exists(os.path.join(output_dir, "API")) : + os.mkdir(os.path.join(output_dir, "API")) + + +# searches through the detaileddescription section +# for parameter documentation with the same name +# and returns it +def get_parameter_documentation(functiondefinition, declname) : + details = functiondefinition.find("detaileddescription") + para = details.find("para") + if para is not None : + paralist = para.find("parameterlist") + if paralist is not None : + items = paralist.findall("parameteritem") + for parameteritem in items : + name = parameteritem.find("parameternamelist").find("parametername").text.strip() + + if name == declname.strip() : + # We got the right parameter + description = parameteritem.find("parameterdescription") + if description is not None : + para = description.find("para") + if para is not None and para.text is not None: + return para.text.strip() + else : + return "" + return "" + +def get_extra_data(classdefinition) : + details = classdefinition.find("detaileddescription") + paragraphs = details.findall("para") + for para in paragraphs : + text = para.text.strip() + if text.startswith("{") and text.endswith("}") : + return eval(text) + return {} + +print("Reading files from %s" % input_dir) + +# save all the classes as dictionary with doxygens id as key. +cls = {} + +# First sweep +for filename in os.listdir(input_dir) : + if not os.path.isfile(os.path.join(input_dir, filename)) : + continue + + if not filename.endswith(".xml") : + continue + + # Only classes are documented now + if not filename.startswith("class") : + continue + + # print("Processing %s" % filename) + + root = ElementTree.parse(os.path.join(input_dir, filename)).getroot() + assert root.tag == "doxygen" + assert len(root) == 1 + + class_def = root.find("compounddef") + assert not cls.has_key(class_def.attrib["id"]) + + class_name = class_def.find("compoundname").text + outfilename_base = class_name if not class_name.startswith("mshr::") else class_name[6:] + briefdescription = class_def.find("briefdescription") + # Description should at most one paragraph + assert (len(briefdescription) <= 1) + briefdescription = briefdescription.find("para").text if briefdescription.find("para") is not None else "" + + data = {'filename' : outfilename_base, + 'description' : briefdescription.strip() } + + extra_data = get_extra_data(class_def) + data.update(extra_data) + + cls[class_def.attrib["id"]] = (class_def, data) + +longest_class_name = max([ len(c[0].find("compoundname").text) for k, c in cls.iteritems()]) +longest_description = max([ len(c[1]["description"]) for k, c in cls.iteritems()]) + +primitives2d = [] +primitives3d = [] +operators = [] +others = [] + + +# Second sweep +for k,c in cls.iteritems() : + theclass = c[0] + classname = theclass.find("compoundname").text + classid = theclass.attrib["id"] + print "Processing "+classname + + classpage = [] + category = others + + # inheritance + # also decides which category the class belong to + inheritance = theclass.find("inheritancegraph") + if inheritance is not None : + inheritancenodes = {} + nodes = inheritance.findall("node") + + # collect all inheritance nodes in a dictionary with the refid (which is not the same + # as the class id as key + myid = False + for x in nodes : + inheritancenodes[x.attrib["id"]] = x + link = x.find("link") + + # save the refid to self + if link is not None and link.attrib["refid"] == classid : + myid = x.attrib["id"] + + # traverse the tree from self through all super classes + current = inheritancenodes[myid] + current = inheritancenodes[current.find("childnode").attrib["refid"]] + inheritance_list = [] + while current is not None : + label = current.find("label").text + if label == "mshr::CSGPrimitive2D" : category = primitives2d + elif label == "mshr::CSGPrimitive3D" : category = primitives3d + elif label == "mshr::CSGOperator" : category = operators + + ref = current.find("link") + + if ref is not None and cls.has_key(ref.attrib["refid"]) : + inheritance_list.append("[%s](%s)" % (current.find("label").text.strip(), + cls[ref.attrib["refid"]][1]["filename"])) + else : + inheritance_list.append(x.find("label").text.strip()) + + child = current.find("childnode") + if child is not None : + current = inheritancenodes[child.attrib["refid"]] + else : + current = None + + + inheritance_list.reverse() + classpage.append("_" + " > ".join(inheritance_list) + "_") + classpage.append("\n\n") + + icon_txt = " |" + if c[1].has_key("small-icon") : + icon_txt = "![%s icon](icons/%s)|" % (classname, c[1]["small-icon"]) + + print "Appending to category" + category.append("%s[%s](API/%s)|%s|\n" % (icon_txt, + classname.ljust(longest_class_name), + c[1]["filename"], + c[1]["description"].ljust(longest_description))) + + + + classpage.append("# %s\n\n" % (classname)) + classpage.append("_%s_\n\n" % c[1]["description"]) + + classpage.append("#### Public functions\n\n") + for member in c[0].findall("sectiondef") : + + # Only public functions are considered part of the public API for now + if member.attrib["kind"] != "public-func" : + continue + + for definition in member.findall("memberdef") : + membername = definition.find("name").text + + argsstr = definition.find("argsstring").text + description = definition.find("briefdescription").find("para") + description = description.text.strip() if description is not None else "" + + returntype = definition.find("type").text + if returntype is not None and len(returntype) > 0 : + returntype = returntype+" " + if returntype is None : + returntype = "" + + # name + classpage.append("%s**%s**%s\n" % (returntype, membername, argsstr)) + + # description + if description : + classpage.append("> _%s_\n" % description) + + classpage.append("\n") + + param_table = [] + + # table of parameters + for param in definition.findall("param") : + declname = param.find("declname").text + paramtype = param.find("type").text + if paramtype is None : + paramtype = "" + + desc = get_parameter_documentation(definition, declname) + param_table.append( (declname, paramtype, desc) ) + + if len(param_table) > 0 : + longest_param_name = max([len(x[0]) for x in param_table]) + longest_param_type = max([len(x[1]) for x in param_table]) + longest_param_desc = max([len(x[2]) for x in param_table]) + + longest_param_name = max(longest_param_name, len("Parameters")) + + classpage.append("> %s|%s|%s\n" % ("Parameters".ljust(longest_param_name), + " "*longest_param_type, + " "*longest_param_desc)) + classpage.append("> %s|%s|%s\n" % ("-"*longest_param_name, + "-"*longest_param_type, + "-"*longest_param_desc)) + + for p in param_table : + classpage.append("> %s|%s|%s\n" % (p[0].ljust(longest_param_name), + p[1].ljust(longest_param_type), + p[2].ljust(longest_param_desc))) + + classpage.append("\n") + + + with open(os.path.join(output_dir, "API", c[1]["filename"]+".md"), "w") as f: + f.write("".join(classpage)) + f.close() + + + +# Create the main page for +mainpage = [""" + +# API reference + +"""] + +mainpage.append("- |%s|%s|\n" % ("2D primitives".ljust(longest_class_name), + "Description".ljust(longest_description))) + +mainpage.append("--|%s|%s|\n" % ( ":".ljust(longest_class_name, "-"), + ":".ljust(longest_description, "-"))) + + +################# Append 2D primitives +#mainpage.append("%s|%s|\n" % ("**2D primitives**".ljust(longest_class_name), +# "".ljust(longest_description))) + +# mainpage.append("%s|%s|\n" % ( ":".ljust(longest_class_name, "-"), +# ":".ljust(longest_description, "-"))) + +mainpage.append("".join(primitives2d)) +#mainpage.append("\n\n") + +################ Append 3D primitives +mainpage.append("- |%s|%s|\n" % ("**3D primitives**".ljust(longest_class_name), + " "*longest_description)) + +# mainpage.append("%s|%s|\n" % ( ":".ljust(longest_class_name, "-"), +# ":".ljust(longest_description, "-"))) + +mainpage.append("".join(primitives3d)) +#mainpage.append("\n\n") + +############### Append operators +mainpage.append("- |%s|%s|\n" % ("**Operators**".ljust(longest_class_name), + " "*longest_description)) + +# mainpage.append("%s|%s|\n" % ( ":".ljust(longest_class_name, "-"), +# ":".ljust(longest_description, "-"))) + +mainpage.append("".join(operators)) +# mainpage.append("\n\n") + +############### Append other classes +mainpage.append("- |%s|%s|\n" % ("**Other classes**".ljust(longest_class_name), + " "*longest_description)) + +# mainpage.append("%s|%s|\n" % ( ":".ljust(longest_class_name, "-"), +# ":".ljust(longest_description, "-"))) + +mainpage.append("".join(others)) +mainpage.append("\n\n") + +# Read the current revision from git +revision_info = subprocess.check_output(["git", "log", "--pretty=format:'%ad %H'", "-1"]).strip("'") +mainpage.append("_Generated from revision: {}. Please don't edit these files manually._\n".format(revision_info)) + + + +with open(os.path.join(output_dir, "API.md"), "w") as f: + f.write("".join(mainpage)) + f.close() + diff --git a/doc/sphinx/Makefile b/doc/sphinx/Makefile new file mode 100644 index 0000000..06a261d --- /dev/null +++ b/doc/sphinx/Makefile @@ -0,0 +1,216 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/mshr.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/mshr.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/mshr" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/mshr" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/doc/sphinx/README b/doc/sphinx/README new file mode 100644 index 0000000..049ff11 --- /dev/null +++ b/doc/sphinx/README @@ -0,0 +1,20 @@ +==================== +Sphinx documentation +==================== + +mshr is documented using Sphinx and reStructured text. The +documentation is hosted at http://fenics-mshr.readthedocs.org/. The +online documentation is automatically updated upon pushes to the mshr +master branch. + + +Building the documentation locally +================================== + +The HTML documentation can be built locally using:: + + make html + +In order for changes in the docstring to be propagated to the html +pages, mshr must be installed anew. In other words, sphinx reads the +docstrings from the installed code, not the source code. diff --git a/doc/sphinx/source/ChangeLog.rst b/doc/sphinx/source/ChangeLog.rst new file mode 120000 index 0000000..6b0be0e --- /dev/null +++ b/doc/sphinx/source/ChangeLog.rst @@ -0,0 +1 @@ +../../../ChangeLog.rst \ No newline at end of file diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py new file mode 100644 index 0000000..8b50642 --- /dev/null +++ b/doc/sphinx/source/conf.py @@ -0,0 +1,342 @@ +# -*- coding: utf-8 -*- +# +# mshr documentation build configuration file, created by +# sphinx-quickstart on Thu Aug 11 13:48:10 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +from __future__ import print_function +import sys +import os + + +def grep_ver(verstr, lines): + """Based on a loose criterion find a string of type:: + + set(MSHR_VERSION_verstr number) + + or:: + + set(MSHR_VERSION_verstr "number") + + in provided iterable of strings and return `number`.""" + + left = "set(MSHR_VERSION_{}".format(verstr) + right = ")" + + line, = [l for l in lines if left in l] + line = line.strip() # strip whitespace + + begin, sep, end = line.partition(left) + assert len(begin) == 0 and sep == left + line = end + + begin, sep, end = line.partition(right) + assert len(end) == 0 and sep == right + line = begin + + line = line.strip(' \'"') # strip whitespace and quotes + int(line) # check integer + + return line + + +def get_mshr_version(): + "Parse `CMakeLists.txt` for mshr version and return as string" + filepath = os.path.join(os.path.pardir, os.path.pardir, os.path.pardir, + "CMakeLists.txt") + with open(filepath, "r") as f: + lines = f.readlines() + major = grep_ver("MAJOR", lines) + minor = grep_ver("MINOR", lines) + micro = grep_ver("MICRO", lines) + release = grep_ver("RELEASE", lines) + if release == "1": + ver_str = ".".join((major, minor, micro)) + else: + assert release == "0" + ver_str = ".".join((major, minor, micro, "dev0")) + print("Detected mshr version '{}'".format(ver_str)) + return ver_str + + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'mshr' +copyright = u'2016, FEniCS Project' +author = u'FEniCS Project' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +mshr_version = get_mshr_version() +# The short X.Y version. +version = mshr_version +# The full version, including alpha/beta/rc tags. +release = mshr_version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +#html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'mshrdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'mshr.tex', u'mshr Documentation', + u'FEniCS Project', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'mshr', u'mshr Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'mshr', u'mshr Documentation', + author, 'mshr', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/doc/sphinx/source/index.rst b/doc/sphinx/source/index.rst new file mode 100644 index 0000000..3355e9b --- /dev/null +++ b/doc/sphinx/source/index.rst @@ -0,0 +1,28 @@ +mshr documentation +================== + +Mshr manual is located on `Bitbucket wiki +`_. +It is waiting for porting to Read the Docs. Contributions +are welcome and first best discussed on `FEniCS Slack +#documentation channel +`_, +see `instruction for joining `_. + +Automatically generated API documentation is not available yet. +The approach from DOLFIN could possibly be ported to mshr too. +Again, contributions are welcome. + +Contents: + +.. toctree:: + :maxdepth: 1 + + ChangeLog + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/fenics-dev-install.sh b/fenics-dev-install.sh new file mode 100755 index 0000000..b045aab --- /dev/null +++ b/fenics-dev-install.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# +# Developer script for configure + build + rebuild. +# +# Notes: +# +# - This script is what most developers use to build/rebuild this package. +# - This script works for both CMake and distutils based packages. +# - If this script is updated in one package, please propagate to the others! +# +# Environment variables: +# +# - $PROCS : controls number of processes to use for build +# : defaults to 6 +# - $FENICS_PYTHON_EXECUTABLE : name of python executable +# : defaults to "python" +# - $FENICS_INSTALL_PREFIX : path to FEniCS installation prefix +# : defaults to "${HOME}/opt/" + +# Exit on first error +set -e + +# Get branch name +BRANCH=`(git symbolic-ref --short HEAD 2> /dev/null || git describe HEAD) | sed s:/:.:g` +echo "On branch '${BRANCH}'." + +# Get installation prefix +: ${FENICS_INSTALL_PREFIX:="${HOME}/opt/fenics/${BRANCH}"} +echo "Installation prefix set to '${FENICS_INSTALL_PREFIX}'." + +# Get Python executable and version +: ${FENICS_PYTHON_EXECUTABLE:=python} +FENICS_PYTHON_VERSION=$(${FENICS_PYTHON_EXECUTABLE} -c 'import sys; print(".".join(map(str, sys.version_info[:2])))') +echo "Python executable and version set to '${FENICS_PYTHON_EXECUTABLE} ${FENICS_PYTHON_VERSION}'." + +# Get number of processes to use for build +: ${PROCS:=6} + + +# Build and install distutils based FEniCS package +if [ -e setup.py ]; then + ${FENICS_PYTHON_EXECUTABLE} setup.py build + ${FENICS_PYTHON_EXECUTABLE} setup.py install --prefix=${FENICS_INSTALL_PREFIX} +fi + + +# Build and install dolfin +if [ -e CMakeLists.txt ]; then + # Set build directory + if [ "${BRANCH}" = "master" ]; then + BUILD_DIR=build.${BRANCH} + elif [ "${BRANCH}" = "next" ]; then + BUILD_DIR=build.${BRANCH} + else + BUILD_DIR="build.wip" # use for all other branches to save disk space + fi + + # Configure + CMAKE_EXTRA_ARGS=$@ + mkdir -p ${BUILD_DIR} + cd ${BUILD_DIR} + time cmake -DCMAKE_INSTALL_PREFIX=${FENICS_INSTALL_PREFIX} \ + -DDOLFIN_ENABLE_TESTING=true \ + -DDOLFIN_ENABLE_BENCHMARKS=true \ + -DCMAKE_BUILD_TYPE=Developer \ + -DDOLFIN_DEPRECATION_ERROR=false \ + ${CMAKE_EXTRA_ARGS} \ + .. + + # Build and install + time make -j ${PROCS} -k && make install -j ${PROCS} +fi + + +# Write config file +CONFIG_FILE="${FENICS_INSTALL_PREFIX}/fenics.conf" +rm -f ${CONFIG_FILE} +cat << EOF > ${CONFIG_FILE} +# FEniCS configuration file created by fenics-dev-install.sh on $(date) +export FENICS_INSTALL_PREFIX=${FENICS_INSTALL_PREFIX} +export FENICS_PYTHON_EXECUTABLE=${FENICS_PYTHON_EXECUTABLE} +export FENICS_PYTHON_VERSION=${FENICS_PYTHON_VERSION} + +# Source FEniCS dependencies if found +FENICS_DEPS_CONF=\${HOME}/opt/fenics/fenics.deps +if [ -e \${FENICS_DEPS_CONF} ]; then + source \${FENICS_DEPS_CONF} +fi + +# Common Unix variables +export LD_LIBRARY_PATH=\${FENICS_INSTALL_PREFIX}/lib:\${LD_LIBRARY_PATH} +export PATH=\${FENICS_INSTALL_PREFIX}/bin:\${PATH} +export PKG_CONFIG_PATH=\${FENICS_INSTALL_PREFIX}/pkgconfig:\${PKG_CONFIG_PATH} +export PYTHONPATH=\${FENICS_INSTALL_PREFIX}/lib/python${FENICS_PYTHON_VERSION}/site-packages:\${PYTHONPATH} +export MANPATH=\${FENICS_INSTALL_PREFIX}/share/man:\${MANPATH} + +# Set Instant cache modules separately for each install +export INSTANT_CACHE_DIR=\${FENICS_INSTALL_PREFIX}/cache/instant + +# CMake search path +export CMAKE_PREFIX_PATH=\${FENICS_INSTALL_PREFIX}:\${CMAKE_PREFIX_PATH} +EOF +if [ $(uname) = "Darwin" ]; then + cat << EOF >> $CONFIG_FILE + +# Mac specific path +export DYLD_FALLBACK_LIBRARY_PATH=\${FENICS_INSTALL_PREFIX}/lib:\${DYLD_FALLBACK_LIBRARY_PATH} +EOF +fi + +# Print information +echo +echo "- Installed branch '${BRANCH}' to ${FENICS_INSTALL_PREFIX}." +echo +echo "- Config file written to ${CONFIG_FILE}" +echo " (source this file)." +echo diff --git a/include/mshr.h b/include/mshr.h new file mode 100644 index 0000000..e7b4441 --- /dev/null +++ b/include/mshr.h @@ -0,0 +1,11 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/include/mshr/ASCFileReader.h b/include/mshr/ASCFileReader.h new file mode 100755 index 0000000..6643955 --- /dev/null +++ b/include/mshr/ASCFileReader.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 Lars Magnus Valnes +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#ifndef __ASC_FILE_READER_H // ?? +#define __ASC_FILE_READER_H // ?? + +#include +#include +#include + +namespace mshr +{ + +class ASCFileReader +{ + public: + /// @brief Read and parse a triangular 3d surface off file + /// @param filename The file to read + /// @param vertices A vector of points to be read into + /// @param facets A vector of facets given as indices to the vertex array. + static void read(const std::string filename, + std::vector >& vertices, + std::vector >& facets); + + static void write(const std::string filename, + const std::vector >& vertices, + const std::vector >& facets); + +}; + +} +#endif diff --git a/include/mshr/CSGCGALDomain2D.h b/include/mshr/CSGCGALDomain2D.h new file mode 100644 index 0000000..35bca27 --- /dev/null +++ b/include/mshr/CSGCGALDomain2D.h @@ -0,0 +1,98 @@ +// Copyright (C) 2013-2017 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +#ifndef __MSHR_CSGCGAL_DOMAIN2D_H +#define __MSHR_CSGCGAL_DOMAIN2D_H + +#include + +#include +#include + + +namespace mshr +{ +// Forward declaration +struct CSGCGALDomain2DImpl; + +/// @brief Polygonal meshing domain +/// +/// Represents the polygonal meshing domain to be sent to the mesh +/// generator. It uses CGAL's Polygon_2 as backend and utilizied CGAL's +/// 2D Regularized Boolean Set-Operations package. +class CSGCGALDomain2D : public dolfin::Variable +{ + public: + /// @brief Create empty polygon + CSGCGALDomain2D(); + + /// @brief Construct polygon from Dolfin CSG geometry + CSGCGALDomain2D( + std::shared_ptr geometry, + double segment_granularity + ); + + /// @brief Destructor + ~CSGCGALDomain2D(); + + /// @brief Copy constructor + CSGCGALDomain2D(const CSGCGALDomain2D &other); + + /// @brief Assignment operator + CSGCGALDomain2D &operator=(const CSGCGALDomain2D &other); + + /// @brief Boolean join + void join_inplace(const CSGCGALDomain2D& other); + + /// @brief Boolean intersection + void intersect_inplace(const CSGCGALDomain2D& other, + double truncate_tolerance=-1.); + + /// @brief Boolean difference + void difference_inplace(const CSGCGALDomain2D& other); + + /// @brief Determine if point is inside the polygon. + bool point_in_domain(dolfin::Point p) const; + + /// @brief Compute the radius of the minimum bounding circle. + double compute_boundingcircle_radius() const; + + std::size_t num_polygons() const; + double shortest_edge() const; + + std::vector get_outer_polygon(std::size_t i) const; + + /// @brief get one point per hole, strictly inside the hole. + /// @param holes the returned points + void get_points_in_holes(std::vector& holes) const; + + /// @brief Informal string representation + /// @param verbose Verbosity level + std::string str(bool verbose) const; + + static + std::pair, + std::vector>> + compute_pslg(const std::vector>& domains); + + private: + std::unique_ptr impl; +}; + +} + +#endif diff --git a/include/mshr/CSGCGALDomain3D.h b/include/mshr/CSGCGALDomain3D.h new file mode 100644 index 0000000..0557b59 --- /dev/null +++ b/include/mshr/CSGCGALDomain3D.h @@ -0,0 +1,214 @@ +// Copyright (C) 2013-2015 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +#ifndef __MSHR_CSGCGAL_DOMAIN3D_H +#define __MSHR_CSGCGAL_DOMAIN3D_H + +#include +#include + +#include +#include + +namespace mshr +{ + + // Forward declaration + struct CSGCGALDomain3DImpl; + struct CSGCGALDomain3DQueryStructureImpl; + +class CSGCGALDomain3DQueryStructure +{ + public: + CSGCGALDomain3DQueryStructure(std::unique_ptr impl); + ~CSGCGALDomain3DQueryStructure(); + + std::unique_ptr impl; +}; + +/// @brief A polyhedron meshing domain. +/// +/// This class represents a polyhedral meshing domain which implements boolean +/// operations. It uses CGAL Polyhedron_3 as backend and utilizes CGAL +// Nef_polyhedron for boolean operations. +class CSGCGALDomain3D : public CSGPrimitive3D +{ + public: + /// @brief Create empty polyhedron + CSGCGALDomain3D(); + + /// @brief Construct polyhedron from CSG geometry + CSGCGALDomain3D(std::shared_ptr csg); + + CSGCGALDomain3D(const std::vector>& vertices, + const std::vector>& facets); + + /// @brief Destructor + ~CSGCGALDomain3D(); + + Type getType() const + { return CSGGeometry::TriPolyhedron; } + + /// @brief Insert polyhedron into this object + /// Inserted polyhedron should not intersect + void insert(const CSGCGALDomain3D& p); + + /// @brief Number of vertices in polyhedron + std::size_t num_vertices() const; + + /// @brief Number of facets in polyhedron + std::size_t num_facets() const; + + /// @brief Number of halfedges in polyhedron + std::size_t num_halfedges() const; + + /// @brief Volume of polyhedron (experimental, use with care) + double volume() const; + + /// @brief Is polyhedron (experimental, use with care) + bool is_insideout() const; + + /// @brief Count the number of degenerate facets wrt. the given tolerance + std::size_t num_degenerate_facets(double threshold) const; + + std::pair facet_area_minmax() const; + + /// @brief get length of shortest edge + std::pair edge_length_range() const; + + /// @brief count edges with squared length shorter than tolerance + std::size_t num_short_edges(double tolerance) const; + + /// @brief Test if any facets intersects + bool is_selfintersecting(bool verbose=false) const; + + std::size_t remove_selfintersections(); + + void normalize(); + + /// @brief Save polyhedron to off file + /// @param filename Filename to write to + void save_off(std::string filename) const; + + void save(std::string filename) const; + + // TODO: Define iterators to be more memory friendly + + /// @brief Output vertices in double precision + /// This outputs as a flattened array + std::unique_ptr> get_vertices() const; + + /// @brief Output facets as indices to the vertices array + std::unique_ptr> get_facets() const; + + /// @brief get one point per hole, strictly inside the hole. + /// @param holes the returned points + /// @param q a query structure returned from get_query_structure() + void get_points_in_holes(std::vector& holes, + std::shared_ptr q=std::shared_ptr()) const; + + /// @brief Attempt to remove degenerate facets. + void remove_degenerate_facets(double tolerance); + + /// @brief Attempts to ensure that the preconditions + /// for successfull meshing generation are fullfilled. + void ensure_meshing_preconditions(); + + /// @brief Return data structure that allows fast queries, + /// essentially a wrapper for a CGAL AABB tree of the polyhedron triangles. + /// When performing queries, the user is responsible for the query structure + /// being in sync with the CSGCGALDomain3D object. + std::shared_ptr get_query_structure() const; + + /// Default parameter values + static dolfin::Parameters default_parameters() + { + dolfin::Parameters p("csg_cgal_domain_3d"); + p.add("remove_degenerate", true); + p.add("degenerate_tolerance", 1e-10); + + return p; + } + + /// @brief Informal string representation + /// @param verbose The verbosity level + std::string str(bool verbose) const; + + /// @brief Remove facets, starting from the facets closest to to the given + /// point + void filter_facets(dolfin::Point start, + double threshold, + std::shared_ptr q=std::shared_ptr()); + + void inside_out(); + + /// A hole is a connected sequence of boundary edges + std::size_t num_holes() const; + + /// Disconnected components are parts of the polyhedron that can not be + /// reached through adjacent entities. + std::size_t num_disconnected_components() const; + + /// Keep only the n largest disconnected components. Returns the number of + /// components removed. + std::size_t keep_largest_components(std::size_t n); + + /// get the axis aligned bounding box of the polyhedron, represented as min and + /// max points + std::pair get_aabb() const; + + /// @brief Close and triangulate all holes. Experimental. + void close_holes(); + + /// @brief Close and triangulate hole. Experimental. + bool close_hole(std::size_t hole, std::string method="auto"); + + void list_hole(std::size_t hole) const; + + double sharpest_edge() const; + std::size_t num_sharp_edges(double tolerance) const; + + void smooth_taubin(std::size_t iterations=3); + void smooth_laplacian(double c=1.0); + + /// Do a simple center vertex refinement + /// Note: This decreases the triangle quality + void refine_center_vertex(); + + /// Refine by splitting all edges + void refine_edge_split(); + + // @brief Reconstruct surface from point set. Experimental + std::shared_ptr reconstruct_surface(double expansion=0.0) const; + + // @brief Isotropic remeshing. Experimental + std::shared_ptr remesh_surface(double edge_length, + double sharp_edge_tolerance=60) const; + + /// @brief Return convex hull of vertices as CSGCGALDomain3D object. Experimental + std::shared_ptr convex_hull() const; + + static std::shared_ptr + convex_hull(const std::vector>& point_set); + + // FIXME: Make this private again + // private : + std::unique_ptr impl; +}; + +} +#endif diff --git a/include/mshr/CSGCGALMeshGenerator2D.h b/include/mshr/CSGCGALMeshGenerator2D.h new file mode 100644 index 0000000..c867f48 --- /dev/null +++ b/include/mshr/CSGCGALMeshGenerator2D.h @@ -0,0 +1,80 @@ +// Copyright (C) 2012-2016 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// +// Modified by Johannes Ring, 2012 + +#ifndef __MSHR_CGAL_MESH_GENERATOR2D_H +#define __MSHR_CGAL_MESH_GENERATOR2D_H + +#include + +#include +#include + +// Forward declaration +namespace dolfin{ class Mesh; } + +namespace mshr +{ + + // Forward declaration + class CSGCGALDomain2D; + + /// @brief Mesh generator for Constructive Solid Geometry (CSG) + /// utilizing CGALs 2D Regularized Boolean Set-Operations + class CSGCGALMeshGenerator2D : public dolfin::Variable + { + public : + + /// @brief Create mesh generator + CSGCGALMeshGenerator2D(); + + /// @brief Destructor + ~CSGCGALMeshGenerator2D(); + + /// @brief Generate mesh + /// @param geometry The geometry to be meshed + /// @param mesh The Dolfin mesh object to be returned. Will ble cleared. + /// @param partition (experimental) If true then generate mesh on process 0 and partition and distribute + /// it. If false, then a full mesh is generated on all processes.(only + /// relevant when running in parallel) + std::shared_ptr generate(std::shared_ptr domain, + const std::vector>>& subdomains + = std::vector>>()); + + /// Default parameter values + static dolfin::Parameters default_parameters() + { + dolfin::Parameters p("csg_cgal_meshgenerator"); + + // DEPRECATED! Use cell size + p.add("mesh_resolution", 64.0); + p.add("triangle_shape_bound", 0.125); + p.add("cell_size", 0.25); + p.add("lloyd_optimize", false); + + // shorter edges in the domain will be collapsed before meshing + p.add("edge_truncate_tolerance", 1e-16); + p.add("partition", true); + + return p; + } + }; + +} + +#endif diff --git a/include/mshr/CSGCGALMeshGenerator3D.h b/include/mshr/CSGCGALMeshGenerator3D.h new file mode 100644 index 0000000..4eacd1d --- /dev/null +++ b/include/mshr/CSGCGALMeshGenerator3D.h @@ -0,0 +1,79 @@ +// Copyright (C) 2012 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +#ifndef __MSHR_CGAL_MESH_GENERATOR3D_H +#define __MSHR_CGAL_MESH_GENERATOR3D_H + +#include + +#include +#include + +namespace dolfin{ class Mesh; } + +namespace mshr +{ + + // Forward declaration + class CSGGeometry; + + /// @brief Mesh generator for Constructive Solid Geometry (CSG) + /// utilizing CGALs 3D Mesh generation package. + /// + /// This class gives access to the backend specific meshing + /// criterias throught the parameter system. + class CSGCGALMeshGenerator3D : public dolfin::Variable + { + public : + + /// @brief Create mesh generator + CSGCGALMeshGenerator3D(); + + /// @brief Destructor + ~CSGCGALMeshGenerator3D(); + + /// @brief Generate Dolfin mesh + /// @param domain The polyhedral domain to be meshed + std::shared_ptr generate(std::shared_ptr domain) const; + + /// @brief Default parameter values + static dolfin::Parameters default_parameters() + { + dolfin::Parameters p("csg_cgal_meshgenerator"); + p.add("mesh_resolution", 64.0); + p.add("perturb_optimize", false); + p.add("exude_optimize", false); + p.add("lloyd_optimize", false); + p.add("odt_optimize", false); + p.add("edge_size", 0.025); + p.add("facet_angle", 25.0); + p.add("facet_size", 0.05); + p.add("facet_distance", 0.005); + p.add("cell_radius_edge_ratio", 3.0); + p.add("cell_size", 0.05); + p.add("detect_sharp_features", true); + p.add("feature_threshold", 70.); + + return p; + } + private: + void generate(std::shared_ptr domain, dolfin::Mesh& mesh) const; + }; + +} + +#endif diff --git a/include/mshr/CSGGeometries3D.h b/include/mshr/CSGGeometries3D.h new file mode 100644 index 0000000..dd3fe8c --- /dev/null +++ b/include/mshr/CSGGeometries3D.h @@ -0,0 +1,61 @@ +// Copyright (C) 2012-2017 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// +// Modified by Johannes Ring, 2012 + +#ifndef __MSHR_GEOMETRIES_H +#define __MSHR_GEOMETRIES_H + +#include "CSGGeometry.h" +#include + +#include + +namespace dolfin +{ + class Mesh; +} + +namespace mshr +{ + class CSGGeometries + { + public: + + // A standard LEGO brick starting at the point x with (n0, n1) + // knobs and height n2. The height should be 1 for a thin brick or 3 + // for a regular brick. + static std::shared_ptr lego(std::size_t n0, + std::size_t n1, + std::size_t n2, + dolfin::Point x = dolfin::Point(0,0,0)); + + // A simple propeller with parameters r - radius of center body, R - length of blades, + // w - width of blades and h - thicknes of blades + static std::shared_ptr propeller(double r=0.125, + double R=0.5, + double w=0.3, + double h=0.025, + bool rotate_blades=true, + bool include_tip=false); + + // Import boundary of mesh as CSG geometry + static std::shared_ptr import_mesh(std::shared_ptr mesh); + }; +} + +#endif diff --git a/include/mshr/CSGGeometry.h b/include/mshr/CSGGeometry.h new file mode 100644 index 0000000..f032946 --- /dev/null +++ b/include/mshr/CSGGeometry.h @@ -0,0 +1,109 @@ +// Copyright (C) 2012 Anders Logg and 2013-2017 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// +// Modified by Johannes Ring, 2012 + + +#ifndef __MSHR_GEOMETRY_H +#define __MSHR_GEOMETRY_H + +#include +#include +#include +#include + +#include +#include + +namespace mshr +{ + + /// Geometry described by Constructive Solid Geometry (CSG) + class CSGGeometry : public dolfin::Variable + { + protected: + /// Constructor + CSGGeometry(); + + public: + /// Destructor + virtual ~CSGGeometry(); + + /// Return dimension of geometry + virtual std::size_t dim() const = 0; + + /// Informal string representation + virtual std::string str(bool verbose) const = 0; + + /// Define subdomain. This feature is 2D only. + /// The subdomain is itself a CSGGeometry and the corresponding + /// cells in the resulting will be marked with i + /// If subdomains overlap, the latest added will take precedence. + void set_subdomain(std::size_t i, std::shared_ptr s); + + /// Define subdomain. This feature is 2D only. + /// The subdomain is itself a CSGGeometry and the corresponding + /// cells in the resulting will be marked with i + /// If subdomains overlap, the latest added will take precedence. + void set_subdomain(std::size_t i, CSGGeometry& s); + + /// @brief Has subdomains been set + bool has_subdomains() const; + + /// @brief Return const list of subdomain geometries + const std::list>>& get_subdomains() const { return subdomains; } + + // These functions are (for now) implemented for 2D only. + std::pair estimate_bounding_sphere(std::size_t numSamples=100) const; + + // Compute axis aligned box that is guaranteed to bound the geometry. May overshoot. + virtual std::pair bounding_box() const = 0; + + // Test if given point is inside geometry. 2D only (for now) + virtual bool inside(dolfin::Point p) const = 0; + + // Test if line segment is entirely inside geometry. 2D only (for now) + virtual bool inside(dolfin::Point p1, dolfin::Point p2) const; + + enum Type { Box, + Sphere, + Cylinder, + Tetrahedron, + Ellipsoid, + Surface3D, + Extrude2D, + Circle, + Ellipse, + Rectangle, + Polygon, + Union, + Intersection, + Difference, + Translation, + Scaling, + Rotation, + TriPolyhedron }; + + virtual Type getType() const = 0; + virtual bool is_operator() const = 0; + + private : + std::list > > subdomains; + }; +} + +#endif diff --git a/include/mshr/CSGOperators.h b/include/mshr/CSGOperators.h new file mode 100644 index 0000000..35a58ac --- /dev/null +++ b/include/mshr/CSGOperators.h @@ -0,0 +1,325 @@ +// Copyright (C) 2012 Anders Logg, 2012-2017 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#ifndef __MSHR_OPERATORS_H +#define __MSHR_OPERATORS_H + +#include "CSGGeometry.h" +#include +#include + +#include + +namespace mshr +{ + + /// @brief Base class for csg operators + /// CSGOperator object are internal (non-leaf) nodes in the CSG tree. + class CSGOperator : public CSGGeometry + { + protected: + CSGOperator(); + public: + virtual bool is_operator() const { return true; } + std::size_t dim() const { return dim_;} + + protected : + std::size_t dim_; + }; + + /// @brief Union of CSG geometries + class CSGUnion : public CSGOperator + { + public: + + /// @brief Create union of two geometries + /// @param g0 a CSG geometry + /// @param g1 a CSG geometry + CSGUnion(std::shared_ptr g0, + std::shared_ptr g1); + + /// @brief get informal string representation + /// @param verbose vervosity level + std::string str(bool verbose) const; + + std::pair bounding_box() const; + bool inside(dolfin::Point p) const; + + Type getType() const { return CSGGeometry::Union; } + + const std::shared_ptr _g0; + const std::shared_ptr _g1; + }; + + /// @brief Difference of CSG geometries + class CSGDifference : public CSGOperator + { + public: + + /// @brief Create union of two geometries + /// @param g0 a CSG geometry + /// @param g1 a CSG CSG geometry + CSGDifference(std::shared_ptr g0, + std::shared_ptr g1); + + /// @brief get informal string representation + std::string str(bool verbose) const; + + std::pair bounding_box() const; + bool inside(dolfin::Point p) const; + + Type getType() const { return CSGGeometry::Difference; } + + const std::shared_ptr _g0; + const std::shared_ptr _g1; + }; + + + /// @brief Intersection of CSG geometries + class CSGIntersection : public CSGOperator + { + public: + + /// @brief Create intersection of two geometries + /// @param g0 a CSG geometry + /// @param g1 a CSG geometry + CSGIntersection(std::shared_ptr g0, + std::shared_ptr g1); + + /// @brief get informal string representation + std::string str(bool verbose) const; + + std::pair bounding_box() const; + bool inside(dolfin::Point p) const; + + Type getType() const { return CSGGeometry::Intersection; } + + const std::shared_ptr _g0; + const std::shared_ptr _g1; + + }; + + /// @brief Translate CSG geometry by vector + /// + /// { 'small-icon' : 'translation-small.png' } + class CSGTranslation : public CSGOperator + { + public: + + /// @brief create translation + /// @param g a CSG geometry + /// @param t the translation vector + CSGTranslation(std::shared_ptr g, + dolfin::Point t); + + /// @brief get informal string representation + std::string str(bool verbose) const; + + std::pair bounding_box() const; + bool inside(dolfin::Point p) const; + + Type getType() const { return CSGGeometry::Translation; } + + const std::shared_ptr g; + const dolfin::Point t; + }; + + /// @brief Scale CSG geometry + /// + /// { 'small-icon' : 'scaling-small.png' } + class CSGScaling : public CSGOperator + { + public: + + /// @brief Create scaling + /// @param g a CSG geometry + /// @param scale_factor + CSGScaling(std::shared_ptr g, + double scale_factor); + + /// @brief Scale (translated) geometry. Geometry will be translated, scaled and translated back + /// @param g a CSG geometry + /// @param c translation + /// @param scale_factor the scale factor + CSGScaling(std::shared_ptr g, + dolfin::Point c, + double scale_factor); + + std::string str(bool verbose) const; + + std::pair bounding_box() const; + bool inside(dolfin::Point p) const; + + Type getType() const { return CSGGeometry::Scaling; } + + const std::shared_ptr g; + const dolfin::Point c; + const double s; + const bool translate; + }; + + /// @brief Rotate CSG geometry + /// + /// { 'small-icon' : 'rotation-small.png' } + class CSGRotation : public CSGOperator + { + public: + /// @brief Create 2D rotation (2D only) + /// @param g A CSG geometry + /// @param theta Rotate by theta + CSGRotation(std::shared_ptr g, + double theta); + + /// @brief Create rotation + /// @param g A CSG geometry + /// @param v In 2D: the rotation center. In 3D: the rotation axis. + /// @param theta Radians to rotate. + CSGRotation(std::shared_ptr g, + dolfin::Point v, + double theta); + + /// @brief create 3D rotation + /// @param g A CSG geometry + /// @param rot_axis The rotation axis + /// @param rot_center The rotation center + /// @param theta Radians to rotate + CSGRotation(std::shared_ptr g, + dolfin::Point rot_axis, + dolfin::Point rot_center, + double theta); + Type getType() const { return CSGGeometry::Rotation; } + std::string str(bool verbose) const; + + std::pair bounding_box() const; + bool inside(dolfin::Point p) const; + + const std::shared_ptr g; + const dolfin::Point rot_axis; + const dolfin::Point c; + const double theta; + const bool translate; + }; + + //--- Union operators --- + + /// Create union of two geometries + inline std::shared_ptr operator+(std::shared_ptr g0, + std::shared_ptr g1) + { + return std::shared_ptr(new CSGUnion(g0, g1)); + } + + /// Create union of two geometries + inline std::shared_ptr operator+(CSGGeometry& g0, + std::shared_ptr g1) + { + return reference_to_no_delete_pointer(g0) + g1; + } + + /// Create union of two geometries + inline std::shared_ptr operator+(std::shared_ptr g0, + CSGGeometry& g1) + { + return g0 + reference_to_no_delete_pointer(g1); + } + + /// Create union of two geometries + inline std::shared_ptr operator+(CSGGeometry& g0, + CSGGeometry& g1) + { + return reference_to_no_delete_pointer(g0) + reference_to_no_delete_pointer(g1); + } + + //--- Difference operators --- + + /// Create difference of two geometries + inline std::shared_ptr operator-(std::shared_ptr g0, + std::shared_ptr g1) + { + return std::shared_ptr(new CSGDifference(g0, g1)); + } + + /// Create difference of two geometries + inline std::shared_ptr operator-(CSGGeometry& g0, + std::shared_ptr g1) + { + return reference_to_no_delete_pointer(g0) - g1; + } + + /// Create union of two geometries + inline std::shared_ptr operator-(std::shared_ptr g0, + CSGGeometry& g1) + { + return g0 - reference_to_no_delete_pointer(g1); + } + + /// Create difference of two geometries + inline std::shared_ptr operator-(CSGGeometry& g0, + CSGGeometry& g1) + { + return reference_to_no_delete_pointer(g0) - reference_to_no_delete_pointer(g1); + } + + + //--- Intersection operators --- + + /// Create intersection of two geometries + inline std::shared_ptr operator*(std::shared_ptr g0, + std::shared_ptr g1) + { + return std::shared_ptr(new CSGIntersection(g0, g1)); + } + + /// Create intersection of two geometries + inline std::shared_ptr operator*(CSGGeometry& g0, + std::shared_ptr g1) + { + return reference_to_no_delete_pointer(g0) * g1; + } + + /// Create intersection of two geometries + inline std::shared_ptr operator*(std::shared_ptr g0, + CSGGeometry& g1) + { + return g0 * reference_to_no_delete_pointer(g1); + } + + /// Create intersection of two geometries + inline std::shared_ptr operator*(CSGGeometry& g0, + CSGGeometry& g1) + { + return reference_to_no_delete_pointer(g0) * reference_to_no_delete_pointer(g1); + } + + //--- Translation operators --- + inline std::shared_ptr operator+(std::shared_ptr g, + dolfin::Point t) + { + return std::shared_ptr(new CSGTranslation(g, t)); + } + + //--- Scaling operators --- + inline std::shared_ptr operator*(std::shared_ptr g, + double s) + { + return std::shared_ptr(new CSGScaling(g, s)); + } + +} + +#endif diff --git a/include/mshr/CSGPrimitive.h b/include/mshr/CSGPrimitive.h new file mode 100644 index 0000000..018f55a --- /dev/null +++ b/include/mshr/CSGPrimitive.h @@ -0,0 +1,38 @@ +// Copyright (C) 2012 Anders Logg +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// +// Modified by Benjamin Kehlet, 2012-2013 + +#ifndef __MSHR_PRIMITIVE_H +#define __MSHR_PRIMITIVE_H + +#include "CSGGeometry.h" + +namespace mshr +{ + + /// Base class for Constructive Solid Geometry (CSG) primitives. + class CSGPrimitive : public CSGGeometry + { + public: + virtual bool is_operator() const { return false; } + virtual std::size_t dim() const = 0; + }; + +} + +#endif diff --git a/include/mshr/CSGPrimitives2D.h b/include/mshr/CSGPrimitives2D.h new file mode 100644 index 0000000..d726d14 --- /dev/null +++ b/include/mshr/CSGPrimitives2D.h @@ -0,0 +1,191 @@ +// Copyright (C) 2012 Anders Logg, 2012-2017 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// +// Modified by Johannes Ring, 2012 + + +#ifndef __MSHR_PRIMITIVES_2D_H +#define __MSHR_PRIMITIVES_2D_H + +#include "CSGPrimitive.h" + +#include +#include + + +namespace mshr +{ + + /// @brief Base class for 2D primitives + class CSGPrimitive2D : public CSGPrimitive + { + protected: + CSGPrimitive2D(); + + public: + + /// @brief get the dimension of the geometry (2) + std::size_t dim() const { return 2; } + }; + + /// @brief A 2D circle + /// + /// { "small-icon" : "circle-small.png" } + class Circle : public CSGPrimitive2D + { + public: + + /// @brief Create circle centered at x with radius r. + /// + /// @param c center. + /// @param r radius. + /// @param segments number of segments when computing the polygonal approximation + Circle(dolfin::Point c, double r, std::size_t segments=0); + + /// @brief get informal string representation + /// @param verbose Verbosity level + std::string str(bool verbose) const; + + Type getType() const { return CSGGeometry::Circle; } + + /// @brief get center of circle + dolfin::Point center() const { return c; } + + /// @brief get radius of circle + double radius() const { return _r; } + + /// @brief get number of segments used when computing polygonal approximation + std::size_t segments() const { return _segments; } + + std::pair bounding_box() const; + bool inside(dolfin::Point p) const; + + private: + dolfin::Point c; + double _r; + const std::size_t _segments; + }; + + /// @brief A 2D ellipse + /// + /// { "small-icon" : "ellipse-small.png" } + class Ellipse : public CSGPrimitive2D + { + public: + + /// @brief Create ellipse centered at c with horizontal semi-axis a and + /// vertical semi-axis b. + /// + /// @param c the center + /// @param a the horizontal semi-axis + /// @param b the vertical semi-axis + /// @param segments the resolution when computing polygonal approximation + Ellipse(dolfin::Point c, double a, double b, std::size_t segments=0); + + /// @brief get informal string representation + /// @param verbose Verbosity level + std::string str(bool verbose) const; + + Type getType() const { return CSGGeometry::Ellipse; } + + /// @brief get center of ellipse + dolfin::Point center() const { return c; } + + /// @brief get horizontal semi-axis + double a() const { return _a; } + + /// @brief get vertical semi-axis + double b() const { return _b; } + + /// @brief get resolution when computing polygonal approximation + std::size_t segments() const { return _segments; } + + std::pair bounding_box() const; + bool inside(dolfin::Point p) const; + + + private: + dolfin::Point c; + double _a, _b; + const std::size_t _segments; + }; + + /// @brief A 2D axis aligned rectangle + /// + /// { "small-icon" : "rectangle-small.png" } + class Rectangle : public CSGPrimitive2D + { + public: + + /// @brief Create rectangle defined by two opposite corners + /// + /// @param a first corner. + /// @param b second corner. + Rectangle(dolfin::Point a, dolfin::Point b); + + /// @brief get informal string representation + /// @param verbose Verbosity level + std::string str(bool verbose) const; + + Type getType() const { return CSGGeometry::Rectangle; } + + /// @brief get first corner + dolfin::Point first_corner() const { return a; } + + /// @brief get second corner + dolfin::Point second_corner() const { return b; } + + std::pair bounding_box() const; + bool inside(dolfin::Point p) const; + + private: + const dolfin::Point a, b; + }; + + /// @brief A 2D polygon + /// + /// { 'large-icon' : 'polygon-large.png', 'small-icon' : 'polygon-small.png' } + class Polygon : public CSGPrimitive2D + { + public: + + /// @brief Create polygon defined by the given vertices. Vertices must be in counter-clockwise order and free of self-intersections. + /// + /// @param vertices A vector of dolfin::Points. Vertices are copied into the object. + Polygon(const std::vector& vertices); + + /// @brief get informal string representation + /// @param verbose Verbosity level + std::string str(bool verbose) const; + + Type getType() const { return CSGGeometry::Polygon; } + + /// @brief check if vertices are counter clockwise oriented. + bool ccw() const; + + /// @brief get vertices in polygon. + const std::vector& vertices() const { return _vertices; } + + std::pair bounding_box() const; + bool inside(dolfin::Point p) const; + + private: + const std::vector _vertices; + }; +} + +#endif diff --git a/include/mshr/CSGPrimitives3D.h b/include/mshr/CSGPrimitives3D.h new file mode 100644 index 0000000..433957a --- /dev/null +++ b/include/mshr/CSGPrimitives3D.h @@ -0,0 +1,270 @@ +// Copyright (C) 2012 Anders Logg, 2012-2017 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// +// Modified by Johannes Ring 2012 + +#ifndef __MSHR_PRIMITIVES_3D_H +#define __MSHR_PRIMITIVES_3D_H + +#include "CSGPrimitive.h" +#include "CSGPrimitives2D.h" +#include +#include +#include + +namespace mshr +{ + + /// @brief Base class for 3D primitives + class CSGPrimitive3D : public CSGPrimitive + { + protected: + CSGPrimitive3D(); + + public: + /// @return get dimension of geometry + std::size_t dim() const { return 3; } + + // Compute axis aligned box that is guaranteed to bound the geometry. May overshoot. + std::pair bounding_box() const; + + // Test if given point is inside geometry. 2D only (for now) + bool inside(dolfin::Point p) const; + + + }; + + // TODO: Make sphere a special case of ellipsoid. + /// @brief A 3D sphere + /// + /// { "small-icon" : "sphere-small.png" } + class Sphere : public CSGPrimitive3D + { + public: + + /// @brief Create sphere at c with radius r. + /// + /// @param center center of sphere + /// @param radius radius of sphere + /// @param segments resolution when generating a polyhedral appoximation + Sphere(dolfin::Point center, double radius, std::size_t segments=10); + + /// @brief Informal string representation + /// @param verbose Verbosity level + std::string str(bool verbose) const; + + Type getType() const { return CSGGeometry::Sphere; } + + const dolfin::Point c; + const double r; + const std::size_t _segments; + }; + + /// @brief A 3D axis aligned box + /// + /// { "small-icon" : "box-small.png" } + class Box : public CSGPrimitive3D + { + public: + + /// @brief Create box defined by two opposite corners + /// + /// @param a The first corner + /// @param b The second corner + Box(dolfin::Point a, dolfin::Point b); + + /// @brief Informal string representation + std::string str(bool verbose) const; + + Type getType() const { return CSGGeometry::Box; } + + const dolfin::Point a, b; + }; + + /// @brief A 3D cylinder + /// + /// { "small-icon" : "cylinder-small.png" } + class Cylinder : public CSGPrimitive3D + { + public: + + /// @brief Create cylinder defined by upper and lower center + /// and radius respectively. + /// + /// @param top Center at top of cylinder. + /// @param bottom Center at bottom of cylinder. + /// @param top_radius Radius top of cylinder. + /// @param bottom_radius Radius at botoom of cylinder. + /// @param segments number of faces on the side when generating a polyhedral approximation. + Cylinder(dolfin::Point top, + dolfin::Point bottom, + double top_radius, + double bottom_radius, + std::size_t segments=32); + + /// @brief Informal string representation + /// @param verbose Verbosity level + /// @return The description string + std::string str(bool verbose) const; + + Type getType() const + { return CSGGeometry::Cylinder; } + + const dolfin::Point _top, _bottom; + const double _top_radius, _bottom_radius; + const std::size_t _segments; + }; + + /// @brief A 3D cone. + /// A cone is here just a special case of a cylinder. + /// + /// { "small-icon" : "cone-small.png" } + class Cone : public Cylinder + { + public: + + /// @brief Create cone defined by upper and lower center and bottom radius respectively. + /// @param top Center at top of cone. + /// @param bottom Center at top of cone. + /// @param r bottom radius of cone. + /// @param segments number of faces on the side when generating a polyhedral approximation. + Cone(dolfin::Point top, dolfin::Point bottom, double r, std::size_t segments=32) + : Cylinder(top, bottom, r, 0, segments) {} + }; + + /// @brief A 3D tetrahedron + /// + /// { "small-icon" : "tetrahedron-small.png" } + class Tetrahedron : public CSGPrimitive3D + { + public: + /// @brief Create tetrahedron defined by four corner points. + /// + /// @param a Point + /// @param b Point + /// @param c Point + /// @param d Point + Tetrahedron(dolfin::Point a, + dolfin::Point b, + dolfin::Point c, + dolfin::Point d); + + /// @brief Informal string representation + /// @return The description string + std::string str(bool verbose) const; + + Type getType() const + { return CSGGeometry::Tetrahedron; } + + const dolfin::Point a, b, c, d; + }; + + /// @brief A triangular 3D surface read from file. + /// + /// { "small-icon" : "disk-small.png" } + class Surface3D : public CSGPrimitive3D + { + public: + Surface3D(std::string filename); + + // Create triangulated polyhedron from surface of mesh + Surface3D(std::shared_ptr mesh); + + // Create triangulate polyhedron on surface of subdomain of mesh + Surface3D(std::shared_ptr mesh, std::size_t cell_domain); + + /// @brief Informal string representation + /// @return The description string + std::string str(bool verbose) const; + + Type getType() const + { return CSGGeometry::Surface3D; } + + const std::string _filename; + std::shared_ptr mesh; + + /// @brief Tolerance when merging close vertices + double vertex_tolerance; + + /// @brief Tolerance when removing degenerate facets + double degenerate_tolerance; + + /// @brief Attempt to repair if surface is not topologically valid + bool repair; + + // @brief Read only one connected_component. Only relevant if repair==true + bool single_connected_component; + + int sharp_features_filter; + + /// @brief First facet, when reading only one connect component. + std::size_t first_facet; + + /// @brief Flip all facets + bool flip_facets; + + /// Dump read triangles and edges to off file (usefull for debugging) + std::string debug_dump; + + std::size_t cell_domain; + bool use_cell_domain; + }; + + /// @brief An axis-aligned ellipsoid + class Ellipsoid : public CSGPrimitive3D + { + public: + /// @brief Create axis aligned ellipsoid + /// + /// @param center center of ellipsoid + /// @param a semi-principal axis in x direction + /// @param b semi-principal axis in y direction + /// @param c semi-principal axis in z direction + /// @param segments resolution when generating a polyhedral appoximation + Ellipsoid(dolfin::Point center, double a, double b, double c, std::size_t segments=17); + + /// @brief Informal string representation + /// @return The description string + std::string str(bool verbose) const; + + Type getType() const + { return CSGGeometry::Ellipsoid; } + + const dolfin::Point center; + const double a, b, c; + const std::size_t _segments; + }; + + /// @brief A 2D polygon extruded along the z axis to 3D + class Extrude2D : public CSGPrimitive3D + { + public : + Extrude2D(std::shared_ptr, double z); + + /// @brief Informal string representation + /// @return The description string + std::string str(bool verbose) const; + + Type getType() const + { return CSGGeometry::Extrude2D; } + + std::shared_ptr geometry_2d; + const double z; + }; +} + +#endif diff --git a/include/mshr/DolfinMeshUtils.h b/include/mshr/DolfinMeshUtils.h new file mode 100644 index 0000000..0029841 --- /dev/null +++ b/include/mshr/DolfinMeshUtils.h @@ -0,0 +1,66 @@ +// Copyright (C) 2014-2016 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#ifndef __MSHR_DOLFIN_MESH_UTILS_H +#define __MSHR_DOLFIN_MESH_UTILS_H + +#include +#include + +namespace dolfin { class Mesh; } + +namespace mshr +{ + +class DolfinMeshUtils +{ + public: + /// Compute the smallest and largest cell wrt. volume. + /// @param m The mesh + static std::pair cell_volume_min_max(const dolfin::Mesh& m); + + /// Check that all vertices has at least one incident cell + /// @param m The mesh + static bool has_isolated_vertices(const dolfin::Mesh& m); + + /// Run all implemented checks mesh consistency + /// @param m The mesh + static bool check_mesh(const dolfin::Mesh& m); + + static std::shared_ptr + extract_subdomain(std::shared_ptr, + std::size_t cell_domain); + + /// @brief Glue together two meshes by matching boundary vertices. + /// @param m1 Mesh 1 to merge + /// @param m2 Mesh 2 to merge + /// @param m1_boundary_marker Mark boundary facets the belongs to m1. A value < 0 disables marking. + /// @param m1_boundary_marker Mark boundary facets the belongs to m2. A value < 0 disables marking. + /// @param m1_boundary_marker Mark boundary facets the belongs to the interface between m1 an m2. A value < 0 disables marking. + static std::shared_ptr + merge_meshes(std::shared_ptr m1, + std::shared_ptr m2, + int m1_marker=1, + int m2_marker=2, + int m1_boundary_marker=1, + int m2_boundary_marker=2, + int interface_marker=3); +}; + +} +#endif diff --git a/include/mshr/GlobalInitializer.h b/include/mshr/GlobalInitializer.h new file mode 100644 index 0000000..ad57445 --- /dev/null +++ b/include/mshr/GlobalInitializer.h @@ -0,0 +1,31 @@ +// Copyright (C) 2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +#ifndef __MSHR_GLOBALINITIALIZER_H +#define __MSHR_GLOBALINITIALIZER_H + +class GlobalInitializer +{ + public: + GlobalInitializer(); + ~GlobalInitializer(); + + /// The singleton instance of the GlobalInitializer. + static GlobalInitializer& instance(); +}; + +#endif diff --git a/include/mshr/MeshGenerator.h b/include/mshr/MeshGenerator.h new file mode 100644 index 0000000..7cf3c87 --- /dev/null +++ b/include/mshr/MeshGenerator.h @@ -0,0 +1,41 @@ +// Copyright (C) 2012 Anders Logg and 2012-2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + + +#ifndef __MSHR_MESH_GENERATOR_H +#define __MSHR_MESH_GENERATOR_H + +#include "CSGGeometry.h" + +namespace dolfin +{ + // Forward declarations + class Mesh; +} + +namespace mshr +{ + + /// Generate mesh from CSG geometry + std::shared_ptr + generate_mesh(std::shared_ptr geometry, + double resolution, + std::string backend="cgal"); +} + +#endif diff --git a/include/mshr/Meshes.h b/include/mshr/Meshes.h new file mode 100644 index 0000000..1bd0695 --- /dev/null +++ b/include/mshr/Meshes.h @@ -0,0 +1,27 @@ +// Copyright (C) -2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +#include + +namespace mshr +{ + class UnitSphereMesh : public dolfin::Mesh + { + public: + UnitSphereMesh(std::size_t resolution); + }; +} diff --git a/include/mshr/OFFFileReader.h b/include/mshr/OFFFileReader.h new file mode 100644 index 0000000..6b985b7 --- /dev/null +++ b/include/mshr/OFFFileReader.h @@ -0,0 +1,47 @@ +// Copyright (C) 2015 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#ifndef __OFF_FILE_READER_H +#define __OFF_FILE_READER_H + +#include +#include +#include + +namespace mshr +{ + +class OFFFileReader +{ + public: + /// @brief Read and parse a triangular 3d surface off file + /// @param filename The file to read + /// @param vertices A vector of points to be read into + /// @param facets A vector of facets given as indices to the vertex array. + static void read(const std::string filename, + std::vector >& vertices, + std::vector >& facets); + + static void write(const std::string filename, + const std::vector >& vertices, + const std::vector >& facets); + +}; + +} +#endif diff --git a/include/mshr/STLFileReader.h b/include/mshr/STLFileReader.h new file mode 100644 index 0000000..0a00ee5 --- /dev/null +++ b/include/mshr/STLFileReader.h @@ -0,0 +1,48 @@ +// Copyright (C) 2014 Benjamin Kehlet & 2016 Lars Magnus Valnes +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#ifndef __STL_FILE_READER_H +#define __STL_FILE_READER_H + +#include +#include +#include + +#include + +namespace mshr +{ + +class STLFileReader +{ +public: + + /// @brief Parse STL file. + /// @param filename The file to be read + /// @param vertices array to return vertices in + /// @param facets array to return facets in as indices to the vertex array + static void read(const std::string filename, + std::vector >& vertices, + std::vector >& facets); + static void write(const std::string filename, + std::vector >& vertices, + std::vector >& facets); +}; + +} +#endif diff --git a/include/mshr/SurfaceConsistency.h b/include/mshr/SurfaceConsistency.h new file mode 100644 index 0000000..285a153 --- /dev/null +++ b/include/mshr/SurfaceConsistency.h @@ -0,0 +1,64 @@ + // Copyright (C) 2014 Benjamin Kehlet + // + // This file is part of mshr. + // + // mshr is free software: you can redistribute it and/or modify + // it under the terms of the GNU General Public License as published by + // the Free Software Foundation, either version 3 of the License, or + // (at your option) any later version. + // + // mshr is distributed in the hope that it will be useful, + // but WITHOUT ANY WARRANTY; without even the implied warranty of + // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + // GNU General Public License for more details. + // + // You should have received a copy of the GNU General Public License + // along with mshr. If not, see . + + // OBS! Experimental + + #ifndef _SURFACE_CONSISTENCY_H + #define _SURFACE_CONSISTENCY_H + + #include + #include + #include + #include + + namespace mshr + { + + class SurfaceConsistency + { + public: + + /// Check that the connectivity of the facet is consistent, ie. all edges are + /// shared by exactly two facets. + /// If error is set to True, then an error will be thrown when a duplicated + /// halfedges is encountered. Otherwise, the index of one of the facets will + /// be stored in the set. + static void checkConnectivity(std::vector >& facets, + std::set& duplicating, + bool error); + + static void filterFacets(const std::vector >& facets, + const std::vector >& vertices, + std::size_t start, std::set& skip); + + static std::pair > >, + std::unique_ptr > > > + merge_close_vertices(const std::vector >& facets, + const std::vector >& vertices, + double tolerance); + + static std::size_t remove_null_facets(std::vector>& facets); + + static std::size_t remove_isolated_vertices(std::vector>& vertices, + std::vector>& facets); + + static void orient_component(std::vector >& facets, + std::size_t start); +}; + +} +#endif diff --git a/include/mshr/SurfaceReconstruction.h b/include/mshr/SurfaceReconstruction.h new file mode 100644 index 0000000..4837a0a --- /dev/null +++ b/include/mshr/SurfaceReconstruction.h @@ -0,0 +1,46 @@ +// Copyright (C) 2015 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#ifndef __MSHR_SURFACERECONSTRUCTION_H +#define __MSHR_SURFACERECONSTRUCTION_H + +#include +#include + +namespace mshr +{ + class SurfaceReconstruction + { + public: + static void reconstruct(const std::vector& vertices, + const std::vector& facets, + std::vector>& reconstructed_vertices, + std::vector>& reconstruct_facets, + double expansion); + + static void remesh(double edge_length, + double sharp_edge_tolerance, + const std::vector& vertices, + const std::vector& facets, + std::vector>& remeshed_vertices, + std::vector>& remeshed_facets); + }; + +} + +#endif diff --git a/include/mshr/TetgenMeshGenerator3D.h b/include/mshr/TetgenMeshGenerator3D.h new file mode 100644 index 0000000..84bcda6 --- /dev/null +++ b/include/mshr/TetgenMeshGenerator3D.h @@ -0,0 +1,61 @@ +// Copyright (C) 2012-2015 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +#ifndef __MSHR_TETGEN_MESH_GENERATOR3D_H +#define __MSHR_TETGEN_MESH_GENERATOR3D_H + +#include + +#include +#include + +namespace dolfin{ class Mesh; } + +namespace mshr +{ + + // Forward declaration + class CSGGeometry; + + class TetgenMeshGenerator3D : public dolfin::Variable + { + public : + TetgenMeshGenerator3D(); + ~TetgenMeshGenerator3D(); + + std::shared_ptr generate(std::shared_ptr domain) const; + + /// Default parameter values + static dolfin::Parameters default_parameters() + { + dolfin::Parameters p("tetgen_meshgenerator"); + p.add("mesh_resolution", 64.0); + + p.add("disable_quality_improvement", false); + p.add("preserve_surface", false); + p.add("max_radius_edge_ratio", 2.); + p.add("min_dihedral_angle", 12.); + + // If set to a positive value, this will override "mesh_resolution" + p.add("max_tet_volume", -1.0); + + return p; + } + }; +} + +#endif diff --git a/mshr-config.cmake.in b/mshr-config.cmake.in new file mode 100644 index 0000000..7935ef0 --- /dev/null +++ b/mshr-config.cmake.in @@ -0,0 +1,17 @@ +# - Config file for the mshr package +# It defines the following variables +# MSHR_INCLUDE_DIRS - include directories for mshr +# MSHR_LIBRARIES_DIR - directory where the mshr library is located +# MSHR_LIBRARIES - libraries to link against + +# Compute paths +get_filename_component(MSHR_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +set(MSHR_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@") +set(MSHR_EXTERNAL_INCLUDE_DIRS "@CONF_EXTERNAL_INCLUDE_DIRS@") +set(MSHR_LIBRARIES_DIRS "@CONF_LIBRARIES_DIRS@") +set(MSHR_EXTERNAL_LIBRARIES "@CONF_EXTERNAL_LIBRARIES@") +set(MSHR_CXX_DEFINITIONS "@CONF_CXX_DEFINITIONS@") +set(MSHR_CXX_FLAGS "@CONF_CXX_FLAGS@") +set(MSHR_LIBRARIES mshr) + +set(MSHR_USE_FILE "${MSHR_CMAKE_DIR}/use-mshr.cmake") \ No newline at end of file diff --git a/mshrConfig.cmake.in b/mshrConfig.cmake.in new file mode 100644 index 0000000..9d27c51 --- /dev/null +++ b/mshrConfig.cmake.in @@ -0,0 +1,15 @@ +# - Config file for the mshr package +# It defines the following variables +# MSHR_INCLUDE_DIRS - include directories for mshr +# MSHR_LIBRARIES_DIR - directory where the mshr library is located +# MSHR_LIBRARIES - libraries to link against + +# Compute paths +get_filename_component(MSHR_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +set(mshr_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@") +set(mshr_EXTERNAL_INCLUDE_DIRS "@CONF_EXTERNAL_INCLUDE_DIRS@") +set(mshr_LIBRARIES_DIRS "@CONF_LIBRARIES_DIRS@") +set(mshr_EXTERNAL_LIBRARIES "@CONF_EXTERNAL_LIBRARIES@") +set(mshr_CXX_DEFINITIONS "@CONF_CXX_DEFINITIONS@") +set(mshr_CXX_FLAGS "@CONF_CXX_FLAGS@") +set(mshr_LIBRARIES mshr) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 0000000..4edf566 --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.5.0) + + +# This script is used solely for getting the paths to the dependencies +# of the bindings (ie. pybind11, dolfin and mshr). +# This is written to a json file which is read from setup.py. + +PROJECT(mshr_pybind11_config) + +# Add cmake directory to mshr module path +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +# pybind11_FOUND - true if pybind11 and all required components found on the system +# pybind11_VERSION - pybind11 version in format Major.Minor.Release +# pybind11_INCLUDE_DIRS - Directories where pybind11 and python headers are located. +# pybind11_INCLUDE_DIR - Directory where pybind11 headers are located. +# pybind11_DEFINITIONS - Definitions necessary to use pybind11, namely USING_pybind11. +# pybind11_LIBRARIES - compile flags and python libraries (as needed) to link against. +# pybind11_LIBRARY - empty. +# CMAKE_MODULE_PATH - appends location of accompanying FindPythonLibsNew.cmake and +# pybind11Tools.cmake modules. +find_package(pybind11 CONFIG HINTS ${PYBIND11_DIR} ${PYBIND11_ROOT} + $ENV{PYBIND11_DIR} $ENV{PYBIND11_ROOT}) + + +find_package(DOLFIN) + +find_package(mshr MODULE) + +configure_file("config.json.in" "config.json") + + diff --git a/python/cmake/Findmshr.cmake b/python/cmake/Findmshr.cmake new file mode 100644 index 0000000..21737fd --- /dev/null +++ b/python/cmake/Findmshr.cmake @@ -0,0 +1,51 @@ +#.rst: +# Findmshr +# -------- +# +# Find mshr library +# +# Find the mshr includes and library. This module defines +# +# :: +# +# mshr_INCLUDE_DIRS, where to find mshr.h. +# mshr_LIBRARIES, libraries to link against to use mshr +# mshr_FOUND, If false (0), do not try to use mshr. +# +# +# +# +# +#============================================================================= +find_path(mshr_INCLUDE_DIR mshr.h + DOC "The mshr include directory") + +set(mshr_NAMES ${mshr_NAMES} libmshr mshr) +find_library(mshr_LIBRARY NAMES ${mshr_NAMES} + DOC "The mshr library") + +# handle the QUIETLY and REQUIRED arguments and set mshr_FOUND to TRUE if +# all listed variables are TRUE +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(mshr + REQUIRED_VARS mshr_LIBRARY + mshr_INCLUDE_DIR + VERSION_VAR mshr_VERSION_STRING) + +if(mshr_FOUND) + # FIND_PACKAGE_HANDLE_STANDARD_ARGS sets mshr_FOUND to TRUE not 1 + # which interferes with the json in config.json (must be true not TRUE) + # So set to 1 (following find_package(DOLFIN) ) + set( mshr_FOUND 1 ) + # use by setuptools.Extension, mshr_LIBRARIES must be in a form that appends to -l + # i.e. mshr not libmshr.so + set( mshr_LIBRARIES "mshr" ) + get_filename_component( mshr_LIBRARIES_DIRS ${mshr_LIBRARY} DIRECTORY ) + set( mshr_INCLUDE_DIRS ${mshr_INCLUDE_DIR} ) +else() + set( mshr_FOUND 0) + set( mshr_LIBRARIES_DIRS "." ) + set( mshr_INCLUDE_DIRS "." ) +endif() + +mark_as_advanced(mshr_INCLUDE_DIR mshr_LIBRARY) diff --git a/python/config.json.in b/python/config.json.in new file mode 100644 index 0000000..b12229b --- /dev/null +++ b/python/config.json.in @@ -0,0 +1,16 @@ +{ + "pybind11" : { "found" : ${pybind11_FOUND}, + "version" : "${pybind11_VERSION}", + "include_dir" : "${pybind11_INCLUDE_DIRS}", + "include_dirs" : "${pybind11_INCLUDE_DIR}", + "definitions" : "${pybind11_DEFINITIONS}" + }, + "dolfin" : { "found" : ${DOLFIN_FOUND}, + "include_dirs" : "${DOLFIN_INCLUDE_DIRS}" + }, + "mshr" : { "found" : ${mshr_FOUND}, + "include_dirs" : "${mshr_INCLUDE_DIRS}", + "lib_dirs" : "${mshr_LIBRARIES_DIRS}", + "libs" : "${mshr_LIBRARIES}" + } +} diff --git a/python/mshr/__init__.py b/python/mshr/__init__.py new file mode 100644 index 0000000..33a2b68 --- /dev/null +++ b/python/mshr/__init__.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +"""mshr package""" + +# Copyright (C) 2017 Benjamin Kehlet + +# This file is part of mshr. +# +# mshr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# mshr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with mshr. If not, see . +# + +import dolfin + +from .cpp import Circle +from .cpp import Ellipse +from .cpp import Rectangle +from .cpp import Polygon +from .cpp import Sphere +from .cpp import Box +from .cpp import Cylinder +from .cpp import Cone +from .cpp import Tetrahedron +from .cpp import Surface3D +from .cpp import Ellipsoid +from .cpp import Rectangle +from .cpp import Extrude2D +from .cpp import CSGCGALDomain3D + +from .cpp import CSGIntersection +from .cpp import CSGDifference +from .cpp import CSGUnion +from .cpp import CSGScaling +from .cpp import CSGTranslation +from .cpp import CSGRotation + +from .cpp import CSGGeometries +from .cpp import UnitSphereMesh + +from .cpp import CSGCGALMeshGenerator3D +from .cpp import TetgenMeshGenerator3D + +from .cpp import CSGCGALDomain2D +from .cpp import CSGCGALMeshGenerator2D + + +from .cpp import generate_mesh diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..2dde610 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,43 @@ +import os, sys +import subprocess +import json + +from setuptools import setup, Extension + + +# Call cmake to generate json file with include and link information about dolfin and pybind11 +if not os.path.isfile(os.path.join("build", "config.json")) : + if not os.path.exists("build") : + os.mkdir("build") + cmake_command=["cmake", os.getcwd()] + if os.environ.get('CMAKE_PREFIX_PATH'): + cmake_command.extend(['-DCMAKE_PREFIX_PATH={}'.format(os.environ['CMAKE_PREFIX_PATH'])]) + subprocess.check_call(cmake_command, cwd=os.path.abspath("build")) + +with open(os.path.join("build", "config.json"), 'r') as infile : + config = json.load(infile) + +include_dirs = config["pybind11"]["include_dirs"].split(";") + \ + config["dolfin"]["include_dirs"].split(";") + \ + config["mshr"]["include_dirs"].split(";") + + +mshr_ext = Extension('mshr.cpp', + ['src/mshr.cpp'], + include_dirs=include_dirs, + library_dirs=config['mshr']['lib_dirs'].split(";"), + libraries=config['mshr']['libs'].split(";"), + extra_compile_args=['-std=c++14'], + language='c++14') + + +setup(name = 'mshr', + version = '2019.2.0.dev0', + author = 'FEniCS Project', + description = 'mshr python interface (via pybind11)', + long_description = '', + packages = ["mshr",], + ext_modules = [mshr_ext], + install_requires = ["numpy", "fenics-dolfin"] + #zip_safe = False) + ) diff --git a/python/src/mshr.cpp b/python/src/mshr.cpp new file mode 100644 index 0000000..1bb24c2 --- /dev/null +++ b/python/src/mshr.cpp @@ -0,0 +1,214 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace py = pybind11; + + +PYBIND11_MODULE(cpp, m) +{ + // Create module for C++ wrappers + m.doc() ="mshr python interface"; + + // CSGGeometry + py::class_> (m, "CSGGeometry") + .def("dim", &mshr::CSGGeometry::dim) + .def("set_subdomain", + static_cast s)>(&mshr::CSGGeometry::set_subdomain)) + .def("has_subdomains", &mshr::CSGGeometry::has_subdomains) + .def("inside", static_cast(&mshr::CSGGeometry::inside)) + .def("__mul__", + static_cast(*)(std::shared_ptr, + std::shared_ptr)>(&mshr::operator*), + py::is_operator()) + + .def("__mul__", + static_cast(*)(std::shared_ptr, + double)>(&mshr::operator*), + py::is_operator()) + + .def("__add__", + static_cast(*)(std::shared_ptr, + std::shared_ptr)>(&mshr::operator+), + py::is_operator()) + + .def("__sub__", + static_cast(*)(std::shared_ptr, + std::shared_ptr)>(&mshr::operator-), + py::is_operator()); + + + py::class_>(m, "CSGUnion"); + + py::class_>(m, "CSGIntersection"); + + py::class_>(m, "CSGDifference"); + + py::class_>(m, "CSGScaling") + .def(py::init, double>()); + + py::class_>(m, "CSGTranslation") + .def(py::init, dolfin::Point>()); + + py::class_>(m, "CSGRotation") + .def(py::init, dolfin::Point, double>()) + .def(py::init, double>()); + + + + // Circle + py::class_>(m, "Circle") + .def(py::init(), + py::arg("c"), py::arg("r"), py::arg("segments")=0) + .def("center", &mshr::Circle::center) + .def("radius", &mshr::Circle::radius); + + // Ellipse + py::class_>(m, "Ellipse") + .def(py::init(), + py::arg("c"), py::arg("a"), py::arg("b"), py::arg("segments")=0) + .def("center", &mshr::Ellipse::center) + .def("a", &mshr::Ellipse::a) + .def("b", &mshr::Ellipse::b); + + // Rectangle + py::class_> (m, "Rectangle") + .def(py::init()) + .def("first_corner", &mshr::Rectangle::first_corner) + .def("second_corne", &mshr::Rectangle::second_corner); + + // Polygon + py::class_>(m, "Polygon") + .def(py::init&>()) + .def("ccw", &mshr::Polygon::ccw) + .def("vertices", &mshr::Polygon::vertices); + + // Sphere + py::class_>(m, "Sphere") + .def(py::init(), + py::arg("center"), py::arg("radius"), py::arg("segments")=10); + + // Box + py::class_>(m, "Box") + .def(py::init()); + + // Cylinder + py::class_>(m, "Cylinder") + .def(py::init(), + py::arg("top"), + py::arg("bottom"), + py::arg("top_radius"), + py::arg("bottom_radius"), + py::arg("segments")=32); + + // Cone + py::class_>(m, "Cone") + .def(py::init(), + py::arg("top"), py::arg("bottom"), py::arg("r"), py::arg("segments")=32); + + // Tetrahedron + py::class_>(m, "Tetrahedron") + .def(py::init()); + + // Surface3D (deprecated, use CSGCGALDomain3D) + py::class_>(m, "Surface3D") + .def(py::init()); + + // Ellipsoid + py::class_>(m, "Ellipsoid") + .def(py::init(), + py::arg("center"), + py::arg("a"), py::arg("b"), py::arg("c"), py::arg("segments")=15); + + // Extrude2D + py::class_>(m, "Extrude2D") + .def(py::init, double>()); + + // CSGCGALDomain3D + py::class_>(m, "CSGCGALDomain3D") + .def(py::init>()) + .def("num_vertices", &mshr::CSGCGALDomain3D::num_vertices) + .def("num_facets", &mshr::CSGCGALDomain3D::num_facets) + .def("num_halfedges", &mshr::CSGCGALDomain3D::num_halfedges) + .def("num_degenerate_facets", &mshr::CSGCGALDomain3D::num_degenerate_facets) + .def("facet_area_minmax", &mshr::CSGCGALDomain3D::facet_area_minmax) + .def("edge_length_minmax", &mshr::CSGCGALDomain3D::edge_length_range) + .def("num_short_edges", &mshr::CSGCGALDomain3D::num_short_edges) + .def("volume", &mshr::CSGCGALDomain3D::volume) + .def("ensure_meshing_preconditions", &mshr::CSGCGALDomain3D::ensure_meshing_preconditions) + .def("is_selfintersecting", &mshr::CSGCGALDomain3D::is_selfintersecting) + .def("remove_selfintersectione", &mshr::CSGCGALDomain3D::remove_selfintersections) + .def("save", &mshr::CSGCGALDomain3D::save) + .def("num_disconnected_components", &mshr::CSGCGALDomain3D::num_disconnected_components) + .def("num_holes", &mshr::CSGCGALDomain3D::num_holes) + .def("remesh_surface", &mshr::CSGCGALDomain3D::remesh_surface) + .def("remove_degenerate_facets", &mshr::CSGCGALDomain3D::remove_degenerate_facets) + .def("convex_hull", static_cast(mshr::CSGCGALDomain3D::*)() const>(&mshr::CSGCGALDomain3D::convex_hull)) + ; + + py::class_(m, "CSGGeometries") + .def_static("lego", &mshr::CSGGeometries::lego) + .def_static("propeller", &mshr::CSGGeometries::propeller); + + py::class_(m, "UnitSphereMesh") + .def(py::init()); + + py::class_>(m, "CSGCGALMeshGenerator3D") + .def(py::init<>()) + .def("generate", static_cast(mshr::CSGCGALMeshGenerator3D::*)(std::shared_ptr) const>(&mshr::CSGCGALMeshGenerator3D::generate)); + + py::class_>(m, "CSGCGALDomain2D") + .def(py::init, double>()); + + py::class_>(m, "CSGCGALMeshGenerator2D") + .def(py::init<>()) + .def("generate", &mshr::CSGCGALMeshGenerator2D::generate); + + + py::class_>(m, "TetgenMeshGenerator3D") + .def(py::init<>()) + .def("generate", &mshr::TetgenMeshGenerator3D::generate); + + // generate_mesh + m.def("generate_mesh", + &mshr::generate_mesh, + py::arg("geoemtry"), py::arg("resolution"), py::arg("backend")="cgal"); +} diff --git a/release.conf b/release.conf new file mode 100644 index 0000000..309f558 --- /dev/null +++ b/release.conf @@ -0,0 +1,5 @@ +# Configuration file for fenics-release + +PACKAGE="mshr" +BRANCH="master" +FILES="ChangeLog.rst CMakeLists.txt" diff --git a/src/ASCFileReader.cpp b/src/ASCFileReader.cpp new file mode 100755 index 0000000..a1e16b8 --- /dev/null +++ b/src/ASCFileReader.cpp @@ -0,0 +1,173 @@ +// Copyright (C) 2016 Lars Magnus Valnes +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#include + +#include +#include +#include +#include + +#define BOOST_FILESYSTEM_NO_DEPRECATED +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +template +inline double convert_string(const std::string& s) +{ + std::istringstream is(s); + T val; + is >> val; + + return val; +} + +// get next line of file and trim away whitespace +inline void get_next_line(std::ifstream& file, std::string& line, std::size_t &lineno) +{ + do + { + std::getline(file, line); + boost::algorithm::trim(line); + lineno++; + } while ( !file.eof() && line == ""); +} +} // end anonymous namespace +//----------------------------------------------------------------------------- +namespace mshr +{ + +void ASCFileReader::read(const std::string filename, + std::vector >& vertices, + std::vector >& facets) +{ + dolfin::log(dolfin:: TRACE, "Reading surface from %s ", filename.c_str()); + + vertices.clear(); + facets.clear(); + typedef boost::tokenizer > tokenizer; + + std::ifstream file(filename.c_str()); + if (!file.is_open()) + { + dolfin::dolfin_error("ASCFileReader.cpp", + "open .asc file to read 3D surface", + "Failed to open file"); + } + + std::string line; + std::size_t lineno = 0; + const boost::char_separator sep(" "); + const boost::char_separator sep2(" "); + + get_next_line(file, line, lineno); + // First line is not important + get_next_line(file, line, lineno); + tokenizer tokens(line, sep); + tokenizer::iterator tok_iter = tokens.begin(); + const std::size_t num_vertices = convert_string(*tok_iter); + tok_iter++; + const std::size_t num_facets = convert_string(*tok_iter); + tok_iter++; + dolfin_assert(tok_iter == tokens.end()); + + vertices.reserve(num_vertices); + facets.reserve(num_facets); + + get_next_line(file, line, lineno); + for (std::size_t i = 0; i < num_vertices; i++) + { + tokenizer tokens(line, sep2); + tokenizer::iterator tok_iter = tokens.begin(); + + std::array vertex; + vertex[0] = convert_string(*tok_iter); + tok_iter++; + vertex[1] = convert_string(*tok_iter); + tok_iter++; + vertex[2] = convert_string(*tok_iter); + tok_iter++; + + tok_iter++; + + dolfin_assert(tok_iter == tokens.end()); + vertices.push_back(vertex); + get_next_line(file, line, lineno); + } + for (std::size_t i = 0; i < num_facets; i++) + { + tokenizer tokens(line, sep); + tokenizer::iterator tok_iter = tokens.begin(); + + std::array facet; + facet[0] = convert_string(*tok_iter); + tok_iter++; + facet[1] = convert_string(*tok_iter); + tok_iter++; + facet[2] = convert_string(*tok_iter); + tok_iter++; + tok_iter++; + + dolfin_assert(tok_iter == tokens.end()); + + facets.push_back(facet); + get_next_line(file, line, lineno); + + } + +} +//----------------------------------------------------------------------------- +void ASCFileReader::write(const std::string filename, + const std::vector >& vertices, + const std::vector >& facets) +{ + std::ofstream file(filename); + file.precision(6); + + if (!file.is_open()) + { + dolfin::dolfin_error("ASCFileReader.cpp", + "open file to write asc data", + "Failed to open file"); + } + + file << "#!" << std::endl; + file << vertices.size() << " " << facets.size() << std::endl; + // Orded vertices + for (const std::array& v : vertices) + { + file << v[0] << " " << v[1] << " " << v[2] << " " << 0 << std::endl; + } + + for (const std::array& f : facets) + { + file << f[0] << " " << f[1] << " " << f[2] << " " << 0 << std::endl; + } +} +} diff --git a/src/CSGCGALDomain2D.cpp b/src/CSGCGALDomain2D.cpp new file mode 100644 index 0000000..2bfa192 --- /dev/null +++ b/src/CSGCGALDomain2D.cpp @@ -0,0 +1,881 @@ +// Copyright (C) 2013-2017 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +// This file must be included to get the compiler flags +// They should ideally have been added via the command line, +// but since CGAL configure time is at mshr compile time, we don't +// have access to CGALConfig.cmake at configure time... +#ifndef CGAL_HEADER_ONLY +#include +#endif +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +// Polygon typedefs +//typedef CGAL::Exact_predicates_exact_constructions_kernel Exact_Kernel; +typedef CGAL::Quotient FT; +typedef CGAL::Cartesian Exact_Kernel; + +typedef Exact_Kernel::Point_2 Point_2; +typedef Exact_Kernel::Vector_2 Vector_2; +typedef Exact_Kernel::Segment_2 Segment_2; +typedef Exact_Kernel::Ray_2 Ray_2; +typedef Exact_Kernel::Direction_2 Direction_2; +typedef Exact_Kernel::Aff_transformation_2 Aff_transformation_2; +typedef CGAL::Polygon_2 Polygon_2; +typedef Polygon_2::Vertex_const_iterator Vertex_const_iterator; +typedef CGAL::Polygon_with_holes_2 Polygon_with_holes_2; +typedef Polygon_with_holes_2::Hole_const_iterator Hole_const_iterator; +typedef CGAL::Polygon_set_2 Polygon_set_2; + +// Min enclosing circle typedefs +typedef CGAL::Min_circle_2_traits_2 Min_Circle_Traits; +typedef CGAL::Min_circle_2 Min_circle; +typedef CGAL::Circle_2 CGAL_Circle; + + +namespace +{ +FT get_shortest_edge(const Polygon_set_2& polygon_set) +{ + FT shortest_edge = std::numeric_limits::max(); + + std::list polygon_list; + polygon_set.polygons_with_holes(std::back_inserter(polygon_list)); + + // FIXME: For now it looks only at the outer boundary. + // Should take holes into account as well + for (const Polygon_with_holes_2& p : polygon_list) + { + const Polygon_2& bdr = p.outer_boundary(); + Point_2 prev = bdr.container().back(); + for (Polygon_2::Vertex_const_iterator vit = bdr.vertices_begin(); + vit != bdr.vertices_end(); vit++) + { + shortest_edge = std::min(shortest_edge, (*vit-prev).squared_length()); + prev = *vit; + } + } + return shortest_edge; +} +//----------------------------------------------------------------------------- +Point_2 point_in_polygon(const Polygon_2& p) +{ + // Take the midpoint, s, of a segment, then do a ray shooting in the inwards + // normal direction and return the midpoint between s and the closest ray hit. + + Polygon_2::Edge_const_iterator eit = p.edges_begin(); + const Point_2 source = CGAL::midpoint(eit->source(), eit->target()); + const Aff_transformation_2 tr = Aff_transformation_2(CGAL::ROTATION, + p.orientation() == CGAL::COUNTERCLOCKWISE ? 1 : -1, 0); + + const Ray_2 r(source, tr(Direction_2(*eit))); + Point_2 closest; + FT min_squared_distance = std::numeric_limits::max(); + + eit++; + for (; eit != p.edges_end(); eit++) + { + auto ii = CGAL::intersection(r, *eit); + if (ii) + { + if (const Point_2* pt = boost::get(&*ii)) + { + const FT squared_distance = CGAL::squared_distance(source, *pt); + if (squared_distance < min_squared_distance) + { + closest = *pt; + min_squared_distance = squared_distance; + } + } + else if (const Segment_2* s = boost::get(&*ii)) + { + // If the intersection is a segment, an edge in the polygon is paralell + // to the ray. Simply check both the source and the target point of the + // segment. + + { + const FT squared_distance = CGAL::squared_distance(source, s->source()); + if (squared_distance < min_squared_distance) + { + closest = s->source(); + min_squared_distance = squared_distance; + } + } + + { + const FT squared_distance = CGAL::squared_distance(source, s->target()); + if (squared_distance < min_squared_distance) + { + closest = s->target(); + min_squared_distance = squared_distance; + } + } + } // end intersection is Segment_2 + } + } + return CGAL::midpoint(source, closest); +} +//----------------------------------------------------------------------------- +void truncate_short_edges(Polygon_set_2& polygon_set, + FT tolerance, + const std::set & collapsable_vertices = std::set()) +{ + const FT tolerance_squared = tolerance*tolerance; + + Polygon_set_2 truncated_set; + std::list polygon_list; + polygon_set.polygons_with_holes(std::back_inserter(polygon_list)); + + // FIXME: For now it looks only at the outer boundary. + // Should take holes into account as well + for (const Polygon_with_holes_2& p : polygon_list) + { + Polygon_2 bdr = p.outer_boundary(); + + bool changed = true; + while(changed) + { + changed = false; + + Polygon_2::Vertex_iterator prev = bdr.vertices_end()-1; + + for (Polygon_2::Vertex_iterator vit = bdr.vertices_begin(); + vit != bdr.vertices_end(); vit++) + { + if ( (*vit-*prev).squared_length() < tolerance_squared) + { + if (collapsable_vertices.size() == 0 || collapsable_vertices.find(*vit) != collapsable_vertices.end()) + { + bdr.erase(vit); + changed = true; + break; + } + else if (collapsable_vertices.size() == 0 || collapsable_vertices.find(*prev) != collapsable_vertices.end()) + { + bdr.erase(prev); + changed = true; + break; + } + else + { + // std::cout << "Couldn't erase vertex" << std::endl; + } + } // end if short edge + prev = vit; + } // end vertex iteration + } // end while(changed) + + Polygon_with_holes_2 pwh(bdr); + for (Hole_const_iterator hit = p.holes_begin(); hit != p.holes_end(); ++hit) + { + pwh.add_hole(*hit); + } + truncated_set.insert(pwh); + } + polygon_set = truncated_set; +} +} // end anonymous namespace +//----------------------------------------------------------------------------- +namespace mshr +{ + +struct CSGCGALDomain2DImpl +{ + Polygon_set_2 polygon_set; + + CSGCGALDomain2DImpl(){} + explicit CSGCGALDomain2DImpl(const Polygon_set_2& p) + : polygon_set(p) {} +}; +//----------------------------------------------------------------------------- +Polygon_2 make_circle( + const Circle* c, + const double segment_granularity + ) +{ + unsigned int num_segments = 0; + if (c->segments() > 0) + { + num_segments = c->segments(); + } else { + dolfin_assert(segment_granularity > 0.0); + + num_segments = std::round( + (2 * DOLFIN_PI * c->radius()) / segment_granularity + ); + } + + // Set the minimum number of segments to 5 (to be a bit circle-like) + // (A polygon with less than 3 segments is degenerate) + num_segments = std::max(num_segments, 5u); + + std::vector pts; + pts.reserve(num_segments); + + for (std::size_t i = 0; i < num_segments; i++) + { + const double phi = (2*DOLFIN_PI*i) / num_segments; + const double x = c->center().x() + c->radius()*cos(phi); + const double y = c->center().y() + c->radius()*sin(phi); + pts.push_back(Point_2(x, y)); + } + + return Polygon_2(pts.begin(), pts.end()); +} +//----------------------------------------------------------------------------- +Polygon_2 make_ellipse(const Ellipse* e, + const double segment_granularity) +{ + unsigned int num_segments = 0; + if (e->segments() > 0) { + num_segments = e->segments(); + } + else + { + dolfin_assert(segment_granularity > 0.0); + // https://en.wikipedia.org/wiki/Ellipse#Circumference + const double a_min_b = e->a() - e->b(); + const double a_plus_b = e->a() + e->b(); + const double h = (a_min_b*a_min_b) / (a_plus_b*a_plus_b); + const double arc_length_ellipse_approx = DOLFIN_PI * a_plus_b * ( + 1.0 + 3.0*h / (10 + sqrt(4.0 - 3.0*h)) + ); + num_segments = std::round(arc_length_ellipse_approx / segment_granularity); + } + + // A polygon with less segments than 3 is degenerate + // FIXME: Should this result in an empty polygon? + num_segments = std::max(num_segments, 3u); + + std::vector pts; + pts.reserve(num_segments); + + for (std::size_t i = 0; i < num_segments; i++) + { + const double phi = (2*DOLFIN_PI*i) / num_segments; + const double x = e->center().x() + e->a()*cos(phi); + const double y = e->center().y() + e->b()*sin(phi); + pts.push_back(Point_2(x, y)); + } + + return Polygon_2(pts.begin(), pts.end()); +} +//----------------------------------------------------------------------------- +Polygon_2 make_rectangle(const Rectangle* r) +{ + const double x0 = std::min(r->first_corner().x(), r->second_corner().x()); + const double y0 = std::min(r->first_corner().y(), r->second_corner().y()); + + const double x1 = std::max(r->first_corner().x(), r->second_corner().x()); + const double y1 = std::max(r->first_corner().y(), r->second_corner().y()); + + std::vector pts; + pts.push_back(Point_2(x0, y0)); + pts.push_back(Point_2(x1, y0)); + pts.push_back(Point_2(x1, y1)); + pts.push_back(Point_2(x0, y1)); + + Polygon_2 p(pts.begin(), pts.end()); + + return p; +} +//----------------------------------------------------------------------------- +Polygon_2 make_polygon(const Polygon* p) +{ + std::vector pts; + std::vector::const_iterator v; + for (v = p->vertices().begin(); v != p->vertices().end(); ++v) + pts.push_back(Point_2(v->x(), v->y())); + + return Polygon_2(pts.begin(), pts.end()); +} +//----------------------------------------------------------------------------- +std::unique_ptr do_transformation(const Polygon_set_2& p, Exact_Kernel::Aff_transformation_2 t) +{ + std::unique_ptr result(new CSGCGALDomain2DImpl); + + std::list polygon_list; + p.polygons_with_holes(std::back_inserter(polygon_list)); + + std::list::const_iterator pit; + for (pit = polygon_list.begin(); pit != polygon_list.end(); ++pit) + { + const Polygon_with_holes_2& pwh = *pit; + + // Transform outer boundary + Polygon_with_holes_2 transformed(CGAL::transform(t, pwh.outer_boundary())); + + // Transform holes + for (Hole_const_iterator hit = pwh.holes_begin(); hit != pwh.holes_end(); hit++) + { + transformed.add_hole(CGAL::transform(t, *hit)); + } + + result->polygon_set.insert(transformed); + } + + return result; +} +//----------------------------------------------------------------------------- +CSGCGALDomain2D::CSGCGALDomain2D() + : impl(new CSGCGALDomain2DImpl) +{ + +} +//----------------------------------------------------------------------------- +CSGCGALDomain2D::~CSGCGALDomain2D() +{ +} +//----------------------------------------------------------------------------- +CSGCGALDomain2D::CSGCGALDomain2D(std::shared_ptr geometry, + double segment_granularity) + : impl(new CSGCGALDomain2DImpl) +{ + if (geometry->dim() != 2) + dolfin::dolfin_error("CSGCGALDomain2D.cpp", + "Creating polygonal domain", + "Geometry has dimension %d, expected 2", geometry->dim()); + + + switch (geometry->getType()) + { + case CSGGeometry::Union: + { + std::shared_ptr u = std::dynamic_pointer_cast(geometry); + dolfin_assert(u); + + CSGCGALDomain2D a(u->_g0, segment_granularity); + CSGCGALDomain2D b(u->_g1, segment_granularity); + + impl.swap(a.impl); + impl->polygon_set.join(b.impl->polygon_set); + break; + } + case CSGGeometry::Intersection: + { + auto u = std::dynamic_pointer_cast(geometry); + dolfin_assert(u); + + CSGCGALDomain2D a(u->_g0, segment_granularity); + CSGCGALDomain2D b(u->_g1, segment_granularity); + + impl.swap(a.impl); + impl->polygon_set.intersection(b.impl->polygon_set); + break; + } + case CSGGeometry::Difference: + { + auto u = std::dynamic_pointer_cast(geometry); + dolfin_assert(u); + CSGCGALDomain2D a(u->_g0, segment_granularity); + CSGCGALDomain2D b(u->_g1, segment_granularity); + + impl.swap(a.impl); + impl->polygon_set.difference(b.impl->polygon_set); + break; + } + case CSGGeometry::Translation : + { + auto t = std::dynamic_pointer_cast(geometry); + dolfin_assert(t); + CSGCGALDomain2D a(t->g, segment_granularity); + Exact_Kernel::Aff_transformation_2 translation(CGAL::TRANSLATION, Vector_2(t->t.x(), t->t.y())); + std::unique_ptr transformed = do_transformation(a.impl->polygon_set, translation); + impl.swap(transformed); + break; + } + case CSGGeometry::Scaling : + { + auto t = std::dynamic_pointer_cast(geometry); + dolfin_assert(t); + CSGCGALDomain2D a(t->g, segment_granularity); + Exact_Kernel::Aff_transformation_2 tr(CGAL::IDENTITY); + + // Translate if requested + if (t->translate) + tr = Exact_Kernel::Aff_transformation_2 (CGAL::TRANSLATION, + Vector_2(-t->c.x(), -t->c.y())) * tr; + + // Do the scaling + tr = Exact_Kernel::Aff_transformation_2(CGAL::SCALING, t->s) * tr; + + if (t->translate) + tr = Exact_Kernel::Aff_transformation_2(CGAL::TRANSLATION, + Vector_2(t->c.x(), t->c.y())) * tr; + + std::unique_ptr transformed = do_transformation(a.impl->polygon_set, + tr); + impl.swap(transformed); + break; + } + case CSGGeometry::Rotation : + { + auto t = std::dynamic_pointer_cast(geometry); + dolfin_assert(t); + CSGCGALDomain2D a(t->g, segment_granularity); + Exact_Kernel::Aff_transformation_2 tr(CGAL::IDENTITY); + + // Translate if requested + if (t->translate) + tr = Exact_Kernel::Aff_transformation_2 (CGAL::TRANSLATION, + Vector_2(-t->c.x(), -t->c.y())) * tr; + + // Do the rotation + tr = Exact_Kernel::Aff_transformation_2(CGAL::ROTATION, sin(t->theta), cos(t->theta)) * tr; + + if (t->translate) + tr = Exact_Kernel::Aff_transformation_2(CGAL::TRANSLATION, + Vector_2(t->c.x(), t->c.y())) * tr; + + std::unique_ptr transformed = do_transformation(a.impl->polygon_set, + tr); + impl.swap(transformed); + break; + } + case CSGGeometry::Circle: + { + auto c = std::dynamic_pointer_cast(geometry); + dolfin_assert(c); + impl->polygon_set.insert(make_circle(c.get(), segment_granularity)); + break; + } + case CSGGeometry::Ellipse: + { + auto c = std::dynamic_pointer_cast(geometry); + dolfin_assert(c); + impl->polygon_set.insert(make_ellipse(c.get(), segment_granularity)); + break; + } + case CSGGeometry::Rectangle: + { + auto r = std::dynamic_pointer_cast(geometry); + dolfin_assert(r); + impl->polygon_set.insert(make_rectangle(r.get())); + break; + } + case CSGGeometry::Polygon: + { + auto p = std::dynamic_pointer_cast(geometry); + dolfin_assert(p); + impl->polygon_set.insert(make_polygon(p.get())); + break; + } + default: + dolfin::dolfin_error("CSGCGALMeshGenerator2D.cpp", + "converting geometry to cgal polyhedron", + "Unhandled primitive type"); + } + + // Truncate short edges + // std::cout << "Creating domain, shortest edge: " << CGAL::to_double(get_shortest_edge(impl->polygon_set)) << std::endl; + + truncate_short_edges(impl->polygon_set, 1e-15); + // std::cout << "Truncated, shortest edge: " << CGAL::to_double(get_shortest_edge(impl->polygon_set)) << std::endl; +} +//----------------------------------------------------------------------------- +CSGCGALDomain2D::CSGCGALDomain2D(const CSGCGALDomain2D &other) + : impl(new CSGCGALDomain2DImpl(other.impl->polygon_set)) +{ +} +//----------------------------------------------------------------------------- +CSGCGALDomain2D &CSGCGALDomain2D::operator=(const CSGCGALDomain2D &other) +{ + std::unique_ptr tmp(new CSGCGALDomain2DImpl(other.impl->polygon_set)); + + impl.swap(tmp); + + return *this; +} +//----------------------------------------------------------------------------- +double CSGCGALDomain2D::compute_boundingcircle_radius() const +{ + std::list polygon_list; + impl->polygon_set.polygons_with_holes(std::back_inserter(polygon_list)); + + std::vector points; + + for (std::list::const_iterator pit = polygon_list.begin(); + pit != polygon_list.end(); ++pit) + for (Polygon_2::Vertex_const_iterator vit = pit->outer_boundary().vertices_begin(); + vit != pit->outer_boundary().vertices_end(); ++vit) + points.push_back(*vit); + + Min_circle min_circle (points.begin(), + points.end(), + true); //randomize point order + + return sqrt(CGAL::to_double(min_circle.circle().squared_radius())); +} +//----------------------------------------------------------------------------- +std::size_t CSGCGALDomain2D::num_polygons() const +{ + return impl->polygon_set.number_of_polygons_with_holes(); +} +//----------------------------------------------------------------------------- +std::vector CSGCGALDomain2D::get_outer_polygon(std::size_t i) const +{ + std::vector polygon_list; + impl->polygon_set.polygons_with_holes(std::back_inserter(polygon_list)); + + const Polygon_2& polygon = polygon_list[i].outer_boundary(); + + std::vector res; + res.reserve(polygon.size()); + for (std::size_t j = 0; j < polygon.size(); j++) + { + const Point_2& p = polygon.vertex(j); + res.push_back(dolfin::Point(CGAL::to_double(p.x()), CGAL::to_double(p.y()))); + } + + return res; +} +//----------------------------------------------------------------------------- +void CSGCGALDomain2D::join_inplace(const CSGCGALDomain2D& other) +{ + impl->polygon_set.join(other.impl->polygon_set); +} +//----------------------------------------------------------------------------- +void CSGCGALDomain2D::difference_inplace(const CSGCGALDomain2D& other) +{ + impl->polygon_set.difference(other.impl->polygon_set); +} +//----------------------------------------------------------------------------- +void CSGCGALDomain2D::intersect_inplace(const CSGCGALDomain2D &other, + double truncate_tolerance) +{ + if (truncate_tolerance > 0) + { + // store + std::set original_vertices; + { + std::list polygon_list; + impl->polygon_set.polygons_with_holes(std::back_inserter(polygon_list)); + + // FIXME: For now it looks only at the outer boundary. + // Should take holes into account as well + for (const Polygon_with_holes_2& p : polygon_list) + { + const Polygon_2& bdr = p.outer_boundary(); + for (Polygon_2::Vertex_const_iterator vit = bdr.vertices_begin(); + vit != bdr.vertices_end(); vit++) + { + original_vertices.insert(*vit); + } + + } + } + // Do the actual intersection + impl->polygon_set.intersection(other.impl->polygon_set); + + const FT shortest_edge = get_shortest_edge(impl->polygon_set); + const FT tolerance_squared = truncate_tolerance*truncate_tolerance; + if (shortest_edge < tolerance_squared) + { + truncate_short_edges(impl->polygon_set, + truncate_tolerance, + original_vertices); + } // end + } + else + { + impl->polygon_set.intersection(other.impl->polygon_set); + } +} +//----------------------------------------------------------------------------- +bool CSGCGALDomain2D::point_in_domain(dolfin::Point p) const +{ + const Point_2 p_(p.x(), p.y()); + return impl->polygon_set.oriented_side(p_) == CGAL::ON_POSITIVE_SIDE; +} +//----------------------------------------------------------------------------- +std::string CSGCGALDomain2D::str(bool verbose) const +{ + std::stringstream ss; + const std::size_t num_polygons = impl->polygon_set.number_of_polygons_with_holes(); + ss << " polygon_list; + impl->polygon_set.polygons_with_holes(std::back_inserter(polygon_list)); + for (const Polygon_with_holes_2& p : polygon_list) + { + ss << " Polygon "; + const Polygon_2& bdr = p.outer_boundary(); + // ss << "[" << bdr.size() << "] Vertices: "; + + for (Polygon_2::Vertex_const_iterator vit = bdr.vertices_begin(); + vit != bdr.vertices_end(); vit++) + { + ss << CGAL::to_double(vit->x()) << " " << CGAL::to_double(vit->y()) << ", "; + } + ss << std::endl; + + Point_2 prev = bdr.container().back(); + ss << "Edge lengths:"; + for (Polygon_2::Vertex_const_iterator vit = bdr.vertices_begin(); + vit != bdr.vertices_end(); vit++) + { + ss << " " << sqrt(CGAL::to_double((*vit-prev).squared_length())); + prev = *vit; + } + ss << std::endl; + + + + ss << "[" << p.number_of_holes() << " holes]" << std::endl; + } + } + + ss << ">"; + return ss.str(); +} +//----------------------------------------------------------------------------- +typedef std::map::iterator VertexMapIterator; +typedef std::set>::iterator SegmentIterator; +//----------------------------------------------------------------------------- +VertexMapIterator pslg_split_edge(SegmentIterator si, + Point_2 p, + std::map& vertex_map, + std::vector& vertices, + std::set>& segments) +{ + dolfin_assert(CGAL::do_intersect(Segment_2(vertices[si->first], vertices[si->second]), p)); + dolfin_assert(p != vertices[si->first]); + dolfin_assert(p != vertices[si->second]); + + vertices.push_back(p); + const std::size_t new_vertex_index = vertex_map.size(); + std::pair result = + vertex_map.insert(std::make_pair(p, new_vertex_index)); + dolfin_assert(result.second); + + const std::size_t source_vertex_index = si->first; + const std::size_t target_vertex_index = si->second; + + segments.erase(si); + segments.insert(std::make_pair(new_vertex_index, target_vertex_index)); + segments.insert(std::make_pair(source_vertex_index, new_vertex_index)); + return result.first; +} +//----------------------------------------------------------------------------- + +// Check if point intersects any edge in the interior +static inline SegmentIterator +pslg_segment_intersection(Point_2 p, + const std::vector& vertices, + const std::set>& segments) +{ + for (SegmentIterator it = segments.begin(); it != segments.end(); ++it) + { + const Segment_2 current(vertices[it->first], vertices[it->second]); + if (CGAL::do_intersect(p, current)) + return it; + } + return segments.end(); +} + +//----------------------------------------------------------------------------- +static inline void pslg_add_simple_polygon(std::map& vertex_map, + std::vector& vertices, + std::set>& segments, + const Polygon_2& p) +{ + // Note that this function assumes that all edges are sufficiently long. Short + // edges should be filtered out when preparing the domain (and subdomain) + // polygon_set_2s. + + Point_2 prev = p.vertex(p.size()-1); + + for (std::size_t i = 0; i < p.size(); i++) + { + const Point_2 current = p.vertex(i); + const Segment_2 s(prev, current); + + VertexMapIterator sit = vertex_map.find(s.source()); + if (sit == vertex_map.end()) + { + // Did not find vertex + SegmentIterator si = pslg_segment_intersection(s.source(), vertices, segments); + if (si != segments.end()) + { + sit = pslg_split_edge(si, + s.source(), + vertex_map, + vertices, + segments); + } + else + { + std::pair i = vertex_map.insert(std::make_pair(s.source(), vertex_map.size())); + vertices.push_back(s.source()); + dolfin_assert(i.second); + sit = i.first; + } + } + + VertexMapIterator tit = vertex_map.find(s.target()); + if (tit == vertex_map.end()) + { + SegmentIterator si = pslg_segment_intersection(s.target(), vertices, segments); + if (si != segments.end()) + { + tit = pslg_split_edge(si, + s.target(), + vertex_map, + vertices, + segments); + } + else + { + std::pair i = vertex_map.insert(std::make_pair(s.target(), vertex_map.size())); + vertices.push_back(s.target()); + dolfin_assert(i.second); + tit = i.first; + } + } + + if (segments.count(std::make_pair(tit->second, sit->second)) == 0) + segments.insert(std::make_pair(sit->second, tit->second)); + + prev = current; + } +} +//----------------------------------------------------------------------------- +std::pair, std::vector>> + CSGCGALDomain2D::compute_pslg(const std::vector>& domains) +{ + std::vector vertices; + std::map vertex_map; + std::set> segments; + // std::vector inserted_segments; + + for (const std::pair& domain : domains) + { + const Polygon_set_2& p = domain.second.impl->polygon_set; + + std::list polygon_list; + p.polygons_with_holes(std::back_inserter(polygon_list)); + + for (const Polygon_with_holes_2& pwh : polygon_list) + { + pslg_add_simple_polygon(vertex_map, + vertices, + segments, + pwh.outer_boundary()); + + // Add holes + Hole_const_iterator hit; + for (hit = pwh.holes_begin(); hit != pwh.holes_end(); ++hit) + { + pslg_add_simple_polygon(vertex_map, + vertices, + segments, + *hit); + } + } + } + + std::vector v(vertices.size()); + for (const std::pair& vertex : vertex_map) + { + v[vertex.second] = dolfin::Point(CGAL::to_double(vertex.first.x()), CGAL::to_double(vertex.first.y())); + } + + std::vector> s(segments.begin(), segments.end()); + + { + double shortest_segment = std::numeric_limits::max(); + for (std::pair segment : s) + { + shortest_segment = std::min(shortest_segment, (v[segment.first]-v[segment.second]).norm()); + } + + double closest_points = std::numeric_limits::max(); + for (auto i = v.begin(); i != v.end(); i++) + { + for (auto j = i+1; j != v.end(); j++) + { + closest_points = std::min(closest_points, (*i-*j).norm()); + } + } + + // std::cout << "Num vertices: " << v.size() << ", num edges: " << s.size() << std::endl; + // std::cout << "Shortest edge: " << shortest_segment << ", closest: " << closest_points << std::endl; + } + + return std::make_pair(std::move(v), std::move(s)); +} +//----------------------------------------------------------------------------- +double CSGCGALDomain2D::shortest_edge() const +{ + FT shortest_edge = std::numeric_limits::max(); + + std::list polygon_list; + impl->polygon_set.polygons_with_holes(std::back_inserter(polygon_list)); + + // FIXME: For now it looks only at the outer boundary. + // Should take holes into account as well + for (const Polygon_with_holes_2& p : polygon_list) + { + const Polygon_2& bdr = p.outer_boundary(); + Point_2 prev = bdr.container().back(); + for (Polygon_2::Vertex_const_iterator vit = bdr.vertices_begin(); + vit != bdr.vertices_end(); vit++) + { + shortest_edge = std::min(shortest_edge, (*vit-prev).squared_length()); + prev = *vit; + } + } + + return sqrt(CGAL::to_double(shortest_edge)); +} +//----------------------------------------------------------------------------- +void CSGCGALDomain2D::get_points_in_holes(std::vector& holes) const +{ + std::list polygon_list; + impl->polygon_set.polygons_with_holes(std::back_inserter(polygon_list)); + + for (const Polygon_with_holes_2& pwh : polygon_list) + { + Hole_const_iterator hit; + for (hit = pwh.holes_begin(); hit != pwh.holes_end(); ++hit) + { + const Point_2 p = point_in_polygon(*hit); + holes.push_back(dolfin::Point(CGAL::to_double(p.x()), CGAL::to_double(p.y()))); + } + } +} +} diff --git a/src/CSGCGALDomain3D.cpp b/src/CSGCGALDomain3D.cpp new file mode 100644 index 0000000..a07a1d6 --- /dev/null +++ b/src/CSGCGALDomain3D.cpp @@ -0,0 +1,2336 @@ +// Copyright (C) 2012-2015 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "meshclean.h" +#include "triangulation_refinement.h" +#include "Polyhedron_utils.h" +#include "smoothing.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifndef MSHR_ENABLE_EXPERIMENTAL + #include +#else + #include +#endif +#include +#include +//#include +#include + +#include +#include +#include +#include + +#include + +#define BOOST_FILESYSTEM_NO_DEPRECATED +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + typedef CGAL::Exact_predicates_exact_constructions_kernel Exact_Kernel; + typedef Exact_Kernel::Triangle_3 Exact_Triangle_3; + typedef Exact_Kernel::Triangle_2 Exact_Triangle_2; + typedef Exact_Kernel::Vector_3 Exact_Vector_3; + typedef CGAL::Polyhedron_3 Exact_Polyhedron_3; + typedef Exact_Polyhedron_3::HalfedgeDS Exact_HalfedgeDS; + typedef Exact_Kernel::Point_3 Exact_Point_3; + typedef Exact_Kernel::Point_2 Exact_Point_2; + typedef Exact_Kernel::Vector_3 Vector_3; + typedef Exact_Kernel::Ray_3 Ray_3; + typedef Exact_Kernel::Aff_transformation_3 Aff_transformation_3; + +#ifndef MSHR_ENABLE_EXPERIMENTAL + typedef CGAL::Nef_polyhedron_3 Nef_polyhedron_3; +#endif + +// AABB tree primitives + typedef CGAL::AABB_face_graph_triangle_primitive Primitive; + typedef CGAL::AABB_traits Traits; + typedef CGAL::AABB_tree AABB_Tree; +} + +namespace mshr +{ + struct CSGCGALDomain3DImpl + { + Exact_Polyhedron_3 p; + }; +} + +namespace +{ + //----------------------------------------------------------------------------- + // Convenience routine to make debugging easier. Remove before releasing. + template + inline void add_triangular_facet(Builder& builder, + int v0, int v1, int v2) + { + builder.begin_facet(); + builder.add_vertex_to_facet(v0); + builder.add_vertex_to_facet(v1); + builder.add_vertex_to_facet(v2); + builder.end_facet(); + } + //----------------------------------------------------------------------------- + template + inline void add_vertex(Builder& builder, + const Exact_Point_3& point) + { + builder.add_vertex(point); + } + //----------------------------------------------------------------------------- + // Sphere + //----------------------------------------------------------------------------- + class Build_sphere : public CGAL::Modifier_base + { + public: + Build_sphere(const mshr::Sphere& sphere) : _sphere(sphere) {} + + void operator()( Exact_HalfedgeDS& hds ) + { + std::vector initial_vertices { dolfin::Point( 0.0, 0.0, 1.0 ), + dolfin::Point( 1.0, 0.0, 0.0 ), + dolfin::Point( 0.0,-1.0, 0.0 ), + dolfin::Point(-1.0, 0.0, 0.0 ), + dolfin::Point( 0.0, 1.0, 0.0 ), + dolfin::Point( 0.0, 0.0,-1.0 )}; + + // Note: Some older compilers (eg. gcc on Ubuntu Precise) require std::array to be + // given explicitly in the initializer list. + std::vector > initial_triangles { std::array{{0, 2, 1 }}, + std::array{{0, 3, 2 }}, + std::array{{0, 4, 3 }}, + std::array{{0, 1, 4 }}, + std::array{{5, 4, 1 }}, + std::array{{5, 1, 2 }}, + std::array{{5, 2, 3 }}, + std::array{{5, 3, 4 }} }; + + std::vector vertices; + std::vector > triangles; + if (_sphere._segments > 1 ) + { + refine_triangulation(initial_vertices, + initial_triangles, + _sphere._segments, + vertices, + triangles); + } + else + { + vertices.reserve(initial_vertices.size()); + std::copy(initial_vertices.begin(), initial_vertices.end(), std::back_inserter(vertices)); + + triangles.reserve(initial_triangles.size()); + std::copy(initial_triangles.begin(), initial_triangles.end(), std::back_inserter(triangles)); + } + + CGAL::Polyhedron_incremental_builder_3 builder( hds, true ); + builder.begin_surface(vertices.size(), triangles.size()); + + dolfin::Point center = _sphere.c; + for (const dolfin::Point& p : vertices) + { + const double scaling = _sphere.r/std::sqrt(p.x()*p.x() + p.y()*p.y() + p.z()*p.z()); + add_vertex(builder, Exact_Point_3(center.x() + p.x()*scaling, + center.y() + p.y()*scaling, + center.z() + p.z()*scaling)); + } + + for (const std::array& t : triangles) + { + add_triangular_facet(builder, t[0], t[1], t[2]); + } + + builder.end_surface(); + } + + private: + const mshr::Sphere& _sphere; + }; + //----------------------------------------------------------------------------- + void make_sphere(const mshr::Sphere* s, Exact_Polyhedron_3& P) + { + Build_sphere builder(*s); + P.delegate(builder); + dolfin_assert(P.is_valid()); + dolfin_assert(P.is_closed()); + } + //----------------------------------------------------------------------------- + class Build_box : public CGAL::Modifier_base + { + public: + Build_box(const mshr::Box* box) : _box(box) {} + + void operator()( Exact_HalfedgeDS& hds ) + { + CGAL::Polyhedron_incremental_builder_3 builder(hds, true); + + builder.begin_surface(8, 12); + + const double x0 = std::min(_box->a.x(), _box->b.x()); + const double y0 = std::max(_box->a.x(), _box->b.x()); + + const double x1 = std::min(_box->a.y(), _box->b.y()); + const double y1 = std::max(_box->a.y(), _box->b.y()); + + const double x2 = std::min(_box->a.z(), _box->b.z()); + const double y2 = std::max(_box->a.z(), _box->b.z()); + + add_vertex(builder, Exact_Point_3(y0, x1, x2)); + add_vertex(builder, Exact_Point_3(x0, x1, y2)); + add_vertex(builder, Exact_Point_3(x0, x1, x2)); + add_vertex(builder, Exact_Point_3(x0, y1, x2)); + add_vertex(builder, Exact_Point_3(y0, x1, y2)); + add_vertex(builder, Exact_Point_3(x0, y1, y2)); + add_vertex(builder, Exact_Point_3(y0, y1, x2)); + add_vertex(builder, Exact_Point_3(y0, y1, y2)); + + add_triangular_facet(builder, 1, 3, 2); + add_triangular_facet(builder, 1, 5, 3); + add_triangular_facet(builder, 1, 4, 5); + add_triangular_facet(builder, 4, 7, 5); + add_triangular_facet(builder, 4, 0, 7); + add_triangular_facet(builder, 0, 6, 7); + add_triangular_facet(builder, 0, 2, 6); + add_triangular_facet(builder, 2, 3, 6); + add_triangular_facet(builder, 7, 6, 5); + add_triangular_facet(builder, 6, 3, 5); + add_triangular_facet(builder, 1, 2, 4); + add_triangular_facet(builder, 2, 0, 4); + + builder.end_surface(); + } + + const mshr::Box* _box; + }; + //----------------------------------------------------------------------------- + void make_box(const mshr::Box* b, Exact_Polyhedron_3& P) + { + Build_box builder(b); + P.delegate(builder); + dolfin_assert(P.is_closed()); + dolfin_assert(P.is_valid()); + } + //----------------------------------------------------------------------------- + void make_tetrahedron(const mshr::Tetrahedron* b, Exact_Polyhedron_3& P) + { + P.make_tetrahedron(Exact_Point_3(b->a.x(), b->a.y(), b->a.z()), + Exact_Point_3(b->b.x(), b->b.y(), b->b.z()), + Exact_Point_3(b->c.x(), b->c.y(), b->c.z()), + Exact_Point_3(b->d.x(), b->d.y(), b->d.z())); + } + //----------------------------------------------------------------------------- + // Return some unit vector orthogonal to a + dolfin::Point generate_orthogonal(const dolfin::Point& a) + { + const dolfin::Point b(0, 1, 0); + const dolfin::Point c(0, 0, 1); + + // Find a vector not parallel to a. + const dolfin::Point d = (fabs(a.dot(b)) < fabs(a.dot(c))) ? b : c; + const dolfin::Point orthogonal = a.cross(d); + return orthogonal/orthogonal.norm(); + } + //----------------------------------------------------------------------------- + class Build_cylinder : public CGAL::Modifier_base + { + public: + Build_cylinder(const mshr::Cylinder* cylinder) : _cylinder(cylinder) {} + + void operator()(Exact_HalfedgeDS& hds) + { + const dolfin::Point axis = (_cylinder->_top - _cylinder->_bottom)/(_cylinder->_top - _cylinder->_bottom).norm(); + dolfin::Point initial = generate_orthogonal(axis); + dolfin_assert(dolfin::near(initial.norm(), 1.0)); + + CGAL::Polyhedron_incremental_builder_3 builder(hds, true); + + const int num_sides = _cylinder->_segments; + const bool top_degenerate = dolfin::near(_cylinder->_top_radius, 0.0); + const bool bottom_degenerate = dolfin::near(_cylinder->_bottom_radius, 0.0); + + const int num_vertices = (top_degenerate || bottom_degenerate) ? num_sides+2 : num_sides*2+2; + + builder.begin_surface(num_vertices, num_sides*4); + + const double delta_theta = 2.0 * DOLFIN_PI / num_sides; + for (int i = 0; i < num_sides; ++i) + { + const double theta = i*delta_theta; + const dolfin::Point rotated = initial.rotate(axis, theta); + if (!bottom_degenerate) + { + const dolfin::Point p = _cylinder->_bottom + rotated*_cylinder->_bottom_radius; + const Exact_Point_3 p_(p.x(), p.y(), p.z()); + add_vertex(builder, p_); + } + if (!top_degenerate) + { + const dolfin::Point p = _cylinder->_top + rotated*_cylinder->_top_radius; + const Exact_Point_3 p_(p.x(), p.y(), p.z()); + add_vertex(builder, p_); + } + } + + // The top and bottom vertices + add_vertex(builder, Exact_Point_3(_cylinder->_bottom.x(), _cylinder->_bottom.y(), + _cylinder->_bottom.z())); + add_vertex(builder, Exact_Point_3(_cylinder->_top.x(), _cylinder->_top.y(), + _cylinder->_top.z())); + + // bottom vertex has index num_vertices-2, top vertex has index num_vertices-1 + + // Construct the facets on the side. + // Vertices must be sorted counter clockwise seen from inside. + for (int i = 0; i < num_sides; ++i) + { + if (top_degenerate) + { + add_triangular_facet(builder, (i + 1)%num_sides, num_vertices - 1, i); + } + else if (bottom_degenerate) + { + add_triangular_facet(builder, i, num_vertices - 1, (i + 1) % num_sides); + } + else + { + //Draw the sides as triangles. + const int vertex_offset = i*2; + + // First triangle + add_triangular_facet(builder, vertex_offset, (vertex_offset + 2) % (num_sides*2), vertex_offset + 1); + + // Second triangle + add_triangular_facet(builder, (vertex_offset + 3) % (num_sides*2), vertex_offset + 1, + (vertex_offset + 2) % (num_sides*2)); + } + } + + // Construct the bottom facet. + if (!bottom_degenerate) + { + for (int i = num_sides-1; i >= 0; i -= 1) + { + if (!top_degenerate) + { + add_triangular_facet(builder, num_vertices-2,( (i+1)*2) % (num_sides*2), i*2); + } + else + { + add_triangular_facet(builder, num_vertices-2, (i+1)%num_sides, i); + } + } + } + + // Construct the the top facet + if (!top_degenerate) + { + for (int i = 0; i < num_sides; i++) + { + if (!bottom_degenerate) + { + add_triangular_facet(builder, num_vertices-1, i*2 + 1, ( (i+1)*2)%(num_sides*2) +1); + } + else + { + add_triangular_facet(builder, num_vertices-2, i, (i+1)%num_sides); + } + } + } + + builder.end_surface(); + } + private: + const mshr::Cylinder* _cylinder; + }; + //----------------------------------------------------------------------------- + void make_cylinder(const mshr::Cylinder* c, Exact_Polyhedron_3& P) + { + Build_cylinder builder(c); + P.delegate(builder); + dolfin_assert(P.is_closed()); + dolfin_assert(P.is_valid()); + } + //----------------------------------------------------------------------------- + class Build_ellipsoid : public CGAL::Modifier_base + { + public: + Build_ellipsoid(const mshr::Ellipsoid& ellipsoid) : _ellipsoid(ellipsoid) {} + + void operator()(Exact_HalfedgeDS& hds) + { + std::vector initial_vertices { dolfin::Point( 0.0, 0.0, 1.0 ), + dolfin::Point( 1.0, 0.0, 0.0 ), + dolfin::Point( 0.0,-1.0, 0.0 ), + dolfin::Point(-1.0, 0.0, 0.0 ), + dolfin::Point( 0.0, 1.0, 0.0 ), + dolfin::Point( 0.0, 0.0,-1.0 )}; + + // Note: Some older compilers (eg. gcc on Ubuntu Precise) require std::array to be + // given explicitly in the initializer list. + std::vector > initial_triangles { std::array{{0, 2, 1 }}, + std::array{{0, 3, 2 }}, + std::array{{0, 4, 3 }}, + std::array{{0, 1, 4 }}, + std::array{{5, 4, 1 }}, + std::array{{5, 1, 2 }}, + std::array{{5, 2, 3 }}, + std::array{{5, 3, 4 }} }; + + std::vector vertices; + std::vector > triangles; + if (_ellipsoid._segments > 1 ) + { + refine_triangulation(initial_vertices, + initial_triangles, + _ellipsoid._segments, + vertices, + triangles); + } + else + { + vertices.reserve(initial_vertices.size()); + std::copy(initial_vertices.begin(), initial_vertices.end(), std::back_inserter(vertices)); + + triangles.reserve(initial_triangles.size()); + std::copy(initial_triangles.begin(), initial_triangles.end(), std::back_inserter(triangles)); + } + + CGAL::Polyhedron_incremental_builder_3 builder( hds, true ); + builder.begin_surface(vertices.size(), triangles.size()); + + dolfin::Point center = _ellipsoid.center; + const double a = _ellipsoid.a, b = _ellipsoid.b, c = _ellipsoid.c; + for (const dolfin::Point& p : vertices) + { + const double scaling = 1.0/std::sqrt( (p.x()*p.x())/(a*a) + + (p.y()*p.y())/(b*b) + + (p.z()*p.z())/(c*c) ); + add_vertex(builder, Exact_Point_3(center.x() + p.x()*scaling, + center.y() + p.y()*scaling, + center.z() + p.z()*scaling)); + } + + for (const std::array& t : triangles) + { + add_triangular_facet(builder, t[0], t[1], t[2]); + } + + builder.end_surface(); + } + + const mshr::Ellipsoid& _ellipsoid; + }; + //----------------------------------------------------------------------------- + void make_ellipsoid(const mshr::Ellipsoid* e, Exact_Polyhedron_3& P) + { + Build_ellipsoid builder(*e); + P.delegate(builder); + dolfin_assert(P.is_closed()); + dolfin_assert(P.is_valid()); + } + //----------------------------------------------------------------------------- + template + class BuildFromFacetList : public CGAL::Modifier_base + { + public: + BuildFromFacetList(const std::vector >& vertices, + const std::vector >& facets, + const std::set& facets_to_be_skipped) + : vertices(vertices), facets(facets), facets_to_be_skipped(facets_to_be_skipped){} + void operator()(HDS& hds) + { + std::set isolated_vertices; + if (facets_to_be_skipped.size() > 0) + { + for (std::size_t i = 0; i < vertices.size(); i++) + isolated_vertices.insert(i); + + for (std::size_t j = 0; j < facets.size(); j++) + { + if (facets_to_be_skipped.count(j) == 0) + { + for (auto it2 = facets[j].begin(); it2 != facets[j].end(); it2++) + isolated_vertices.erase(*it2); + } + } + } + + // std::cout << "Isolated vertices: " << isolated_vertices.size() << std::endl; + + CGAL::Polyhedron_incremental_builder_3 builder(hds, true); + + builder.begin_surface(vertices.size(), facets.size()); + + for (std::size_t i = 0; i < vertices.size(); i++) + { + if (isolated_vertices.count(i) == 0) + builder.add_vertex(Exact_Point_3( vertices[i][0], vertices[i][1], vertices[i][2]) ); + } + + const bool has_isolated_vertices = isolated_vertices.size() > 0; + std::vector iv(isolated_vertices.begin(), isolated_vertices.end()); + std::sort(iv.begin(), iv.end()); + for (std::size_t i = 0; i < facets.size(); i++) + { + if (facets_to_be_skipped.count(i) == 0) + { + if (has_isolated_vertices) + { + std::vector f; + f.reserve(facets[i].size()); + for (auto it = facets[i].begin(); it != facets[i].end(); it++) + { + f.push_back(*it - std::distance(iv.begin(), std::lower_bound(iv.begin(), iv.end(), *it))); + } + builder.add_facet(f.begin(), f.end()); + } + else + builder.add_facet(facets[i].begin(), facets[i].end()); + + if (builder.error()) + { + dolfin::dolfin_error("CSGCGALDomain3D.cpp", + "read surface from file", + "error in polyhedron builder"); + } + } + else + { + // std::cout << "Skipping" << std::endl; + } + } + + builder.end_surface(); + + } + const std::vector >& vertices; + const std::vector >& facets; + const std::set& facets_to_be_skipped; + }; + //----------------------------------------------------------------------------- + void make_surface3D(const mshr::Surface3D* s, Exact_Polyhedron_3& P) + { + dolfin_assert(s); + + std::vector > vertices; + std::vector > facets; + std::set skip; + + if (s->_filename == "") + { + dolfin_assert(s->mesh); + + std::unique_ptr b; + + if (s->use_cell_domain) + { + std::shared_ptr m = mshr::DolfinMeshUtils::extract_subdomain(s->mesh, s->cell_domain); + b.reset(new dolfin::BoundaryMesh(*m, "exterior", false)); + } + else + { + // Extract global boundary of mesh, order with outward pointing normals + b.reset(new dolfin::BoundaryMesh(*(s->mesh), "exterior", false)); + } + + for (dolfin::VertexIterator v(*b); !v.end(); ++v) + { + const dolfin::Point& p = v->point(); + vertices.push_back(std::array{{p[0], p[1], p[2]}}); + } + + for (dolfin::CellIterator c(*b); !c.end(); ++c) + { + const unsigned int* vertices = c->entities(0); + facets.push_back(std::array{{vertices[0], vertices[1], vertices[2]}}); + } + } + else + { + boost::filesystem::path fpath(s->_filename); + if (fpath.extension() == ".off") + { + mshr::OFFFileReader::read(s->_filename, vertices, facets); + } + else + { + if (fpath.extension() == ".stl") + { + mshr::STLFileReader::read(s->_filename, vertices, facets); + } + else if (fpath.extension() == ".asc") // ADDED + { + mshr::ASCFileReader::read(s->_filename, vertices, facets); + } + else + { + dolfin::dolfin_error("CSGCGALDomain3D.cpp", + "open file to read 3D surface", + "Unknown file type"); + } + + log(dolfin::TRACE, "Done reading file"); + } + + if (s->flip_facets) + { + log(dolfin::TRACE, "Flipping facets"); + for (std::vector >::iterator it = facets.begin(); + it != facets.end(); it++) + { + std::array& t = *it; + std::swap(t[1], t[2]); + } + } + + // std::pair > >, + // std::unique_ptr > > > filtered = + // SurfaceConsistency::merge_close_vertices(facets, vertices); + + // log(dolfin::TRACE, "Checking connectivity"); + + //mshr::closest_vertices(vertices); + + if (s->repair) + { + { + const std::size_t removed = mshr::SurfaceConsistency::remove_null_facets(facets); + if (removed > 0) + log(dolfin::TRACE, "Removed %u degenerate facets", removed); + } + + { + const std::size_t removed = mshr::SurfaceConsistency::remove_isolated_vertices(vertices, facets); + if (removed > 0) + log(dolfin::TRACE, "Removed %u isolated vertices", removed); + } + + + // TODO: Orient all components + mshr::SurfaceConsistency::orient_component(facets, 0); + // std::size_t start_facet = s->first_facet; + + std::set duplicating; + mshr::SurfaceConsistency::checkConnectivity(facets, duplicating, false); + log(dolfin::TRACE, "%u facets filtered out", duplicating.size()); + skip.insert(duplicating.begin(), duplicating.end()); + + + // SurfaceConsistency::filterFacets(facets, + // vertices, + // start_facet, + // skip); + + // std::vector > filtered_facets; + // filtered_facets.reserve(facets.size()-skip.size()); + // for (std::size_t i = 0; i < facets.size(); i++) + // { + // if (skip.count(i) == 0) + // filtered_facets.push_back(facets[i]); + // } + } + + // std::cout << "Duplicating: " << duplicating.size() << std::endl; + // for (auto it = duplicating.begin(); it != duplicating.end(); it++) + // std::cout << *it << " "; + // std::cout << std::endl; + + } // end read from file + + if (s->debug_dump != "") + { + log(dolfin::TRACE, "Dumping to file '%s'", s->debug_dump.c_str()); + mshr::OFFFileReader::write(s->debug_dump, + vertices, + facets); + } + + + // Create the polyhedron + BuildFromFacetList builder(vertices, facets, skip); + P.delegate(builder); + log(dolfin::TRACE, "Done creating polyhedron"); + + if (!P.is_valid()) + { + dolfin::dolfin_error("CSGCGALDomain3D.cpp", + "read surface from file", + "Polyhedron is not valid. If you are sure your file is valid, please file a bug report"); + } + + P.normalize_border(); + + // { + // std::size_t num_vertices = P.size_of_vertices(); + // std::list components; + // mshr::PolyhedronUtils::get_disconnected_components(P, std::back_inserter(components)); + // std::cout << "Number of components: " << components.size() << std::endl; + // const unsigned int deleted = P.keep_largest_connected_components(1); + // std::cout << "Deleted " << deleted << " disconnected components with " << num_vertices-P.size_of_vertices() << " vertices" << std::endl; + // std::cout << "Min vertex degree before closing: " << mshr::PolyhedronUtils::min_vertex_degree(P) << std::endl; + // std::cout << "Is pure triangular: " << (P.is_pure_triangle() ? "True" : "False") << std::endl; + // int tmp; + // std::cin >> tmp; + // } + + // Triangulate polyhedron + //mshr::PolyhedronUtils::triangulate_polyhedron(P); + dolfin_assert (P.is_pure_triangle()); + + // remove self-intersecting facets + if (s->repair) + { + if (s->sharp_features_filter >= 0) + { + mshr::PolyhedronUtils::filter_sharp_features(P, + s->sharp_features_filter, + DOLFIN_PI/6.0); + } + + // FIXME: Should this be enabled? + // mshr::PolyhedronUtils::remove_self_intersections(P); + // mshr::PolyhedronUtils::close_holes(P); + } + + // mshr::PolyhedronUtils::list_self_intersections(P); + + // if (s->degenerate_tolerance > 0) + // { + // if (remove_degenerate(P, s->degenerate_tolerance)) + // log(dolfin::TRACE, "Removed degenerate facets from '%s'", + // s->_filename.c_str()); + // } + + // if (!P.is_closed()) + // { + // if (s->repair) + // { + // mshr::PolyhedronUtils::cut_holes(P); + + // dolfin_assert(P.is_closed()); + // dolfin_assert(P.is_pure_triangle()); + // remove_degenerate(P, s->degenerate_tolerance); + // } + // else + // { + // dolfin::dolfin_error("CSGCGALDomain3D.cpp", + // "read surface from file", + // "Surface is not closed."); + // } + // } + } +//----------------------------------------------------------------------------- + template + struct Insert_polyhedron_to + : public CGAL::Modifier_base + { + Insert_polyhedron_to(const Polyhedron& in_poly) + : _in_poly(in_poly) {} + + void operator()(typename Polyhedron::HalfedgeDS& hds) + { + typedef typename Polyhedron::HalfedgeDS HDS; + + CGAL::Polyhedron_incremental_builder_3 builder(hds); + + typedef typename Polyhedron::Vertex_const_iterator Vertex_const_iterator; + typedef typename Polyhedron::Facet_const_iterator Facet_const_iterator; + typedef typename Polyhedron::Halfedge_around_facet_const_circulator HFCC; + + builder.begin_surface(_in_poly.size_of_vertices(), + _in_poly.size_of_facets(), + _in_poly.size_of_halfedges()); + + for(Vertex_const_iterator + vi = _in_poly.vertices_begin(), end = _in_poly.vertices_end(); + vi != end ; ++vi) + { + builder.add_vertex(vi->point()); + } + + typedef CGAL::Inverse_index Index; + Index index(_in_poly.vertices_begin(), _in_poly.vertices_end()); + + for(Facet_const_iterator + fi = _in_poly.facets_begin(), end = _in_poly.facets_end(); + fi != end; ++fi) + { + HFCC hc = fi->facet_begin(); + HFCC hc_end = hc; + // std::size_t n = circulator_size( hc); + // CGAL_assertion( n >= 3); + builder.begin_facet (); + do + { + builder.add_vertex_to_facet(index[hc->vertex()]); + ++hc; + } while( hc != hc_end); + builder.end_facet(); + } + builder.end_surface(); + } // end operator()(..) + private: + const Polyhedron& _in_poly; + }; // end Copy_polyhedron_to<> +//----------------------------------------------------------------------------- + template + class BuildExtrude2D : public CGAL::Modifier_base + { + public: + BuildExtrude2D(const mshr::CSGGeometry& polygon, double z) + : polygon(polygon), z(z){} + void operator()(HDS& hds) + { + // Let the 2d mesh generator triangulate the polygon + std::shared_ptr mesh2d(new dolfin::Mesh()); + { + mshr::CSGCGALMeshGenerator2D generator; + generator.parameters["mesh_resolution"] = 2.0; + generator.parameters["partition"] = false; + + const std::pair bounding_circle = polygon.estimate_bounding_sphere(); + + // Generate 2D mesh (on all nodes if in parallel) + std::shared_ptr + d2(new mshr::CSGCGALDomain2D(dolfin::reference_to_no_delete_pointer(polygon), + bounding_circle.second/6.)); + + mesh2d = generator.generate(d2); + } + + CGAL::Polyhedron_incremental_builder_3 builder(hds, true); + + builder.begin_surface(0, 0); + + const double min_z = std::min(0., z); + const double max_z = std::max(0., z); + + // Copy vertices to the new 3d polyhedron + const std::vector& vertices = mesh2d->coordinates(); + for (std::size_t i = 0; i < vertices.size()/2; i++) + { + builder.add_vertex(Exact_Point_3(vertices[2*i], vertices[2*i+1], min_z)); + builder.add_vertex(Exact_Point_3(vertices[2*i], vertices[2*i+1], max_z)); + } + + // Add the triangles from the 2d mesh at z=0 and z=z + for (dolfin::CellIterator c(*mesh2d); !c.end(); ++c) + { + const unsigned int* v_indices = c->entities(0); + const bool flip = Exact_Triangle_2(Exact_Point_2(vertices[2*v_indices[0]], vertices[2*v_indices[0]+1]), + Exact_Point_2(vertices[2*v_indices[1]], vertices[2*v_indices[1]+1]), + Exact_Point_2(vertices[2*v_indices[2]], vertices[2*v_indices[2]+1])).orientation() == CGAL::POSITIVE; + builder.begin_facet(); + builder.add_vertex_to_facet(2*v_indices[0]); + if (flip) + { + builder.add_vertex_to_facet(2*v_indices[2]); + builder.add_vertex_to_facet(2*v_indices[1]); + } + else + { + builder.add_vertex_to_facet(2*v_indices[1]); + builder.add_vertex_to_facet(2*v_indices[2]); + } + + builder.end_facet(); + + builder.begin_facet(); + builder.add_vertex_to_facet(2*v_indices[0]+1); + if (!flip) + { + builder.add_vertex_to_facet(2*v_indices[2]+1); + builder.add_vertex_to_facet(2*v_indices[1]+1); + } + else + { + builder.add_vertex_to_facet(2*v_indices[1]+1); + builder.add_vertex_to_facet(2*v_indices[2]+1); + } + builder.end_facet(); + } + + // Connect the two polygons + dolfin::BoundaryMesh bdr(*mesh2d, "exterior", false); + const dolfin::MeshFunction& vertex_map = bdr.entity_map(0); + for (dolfin::CellIterator cell(bdr); !cell.end(); ++cell) + { + const unsigned int* v_indices = cell->entities(0); + builder.begin_facet(); + builder.add_vertex_to_facet(2*vertex_map[v_indices[0]]); + builder.add_vertex_to_facet(2*vertex_map[v_indices[1]]); + builder.add_vertex_to_facet(2*vertex_map[v_indices[0]]+1); + builder.end_facet(); + + builder.begin_facet(); + builder.add_vertex_to_facet(2*vertex_map[v_indices[1]]); + builder.add_vertex_to_facet(2*vertex_map[v_indices[1]]+1); + builder.add_vertex_to_facet(2*vertex_map[v_indices[0]]+1); + builder.end_facet(); + + } + + builder.end_surface(); + } + + const mshr::CSGGeometry& polygon; + const double z; + }; +// ---- + void make_extrude2D(const mshr::Extrude2D* e, Exact_Polyhedron_3& P) + { + dolfin_assert(e); + + //CSGCGALDomain2D polygon(e->geometry_2d.get()); + BuildExtrude2D builder(*e->geometry_2d, e->z); + P.delegate(builder); + } +//----------------------------------------------------------------------------- + Aff_transformation_3 get_scaling(const mshr::CSGScaling& s) + { + Aff_transformation_3 transformation(CGAL::IDENTITY); + if (s.translate) + { + Aff_transformation_3 translation(CGAL::TRANSLATION, + Vector_3(-s.c.x(), + -s.c.y(), + -s.c.z())); + transformation = translation * transformation; + } + + Aff_transformation_3 scaling(CGAL::SCALING, s.s); + transformation = scaling * transformation; + + if (s.translate) + { + Aff_transformation_3 translation(CGAL::TRANSLATION, + Vector_3(s.c.x(), + s.c.y(), + s.c.z())); + transformation = translation * transformation; + } + + return transformation; + } +//----------------------------------------------------------------------------- + Aff_transformation_3 get_rotation(const mshr::CSGRotation& r) + { + // Normalize rotation axis vector + dolfin::Point axis = r.rot_axis/(r.rot_axis.norm()); + dolfin_assert(dolfin::near(axis.norm(), 1.0)); + + Aff_transformation_3 transformation(CGAL::IDENTITY); + if (r.translate) + { + Aff_transformation_3 translation(CGAL::TRANSLATION, + Vector_3(-r.c.x(), + -r.c.y(), + -r.c.z())); + transformation = translation * transformation; + } + + // The Euler-Rodrigues formula + const double a = cos(r.theta/2); + const double b = -axis.x()*sin(r.theta/2); + const double c = -axis.y()*sin(r.theta/2); + const double d = -axis.z()*sin(r.theta/2); + + Aff_transformation_3 rotation(a*a+b*b-c*c-d*d, + 2*(b*c-a*d), + 2*(b*d+a*c), + 2*(b*c+a*d), + a*a+c*c-b*b-d*d, + 2*(c*d-a*b), + 2*(b*d-a*c), + 2*(c*d+a*b), + a*a+d*d-b*b-c*c); + transformation = rotation * transformation; + + if (r.translate) + { + Aff_transformation_3 translation(CGAL::TRANSLATION, + Vector_3(r.c.x(), + r.c.y(), + r.c.z())); + transformation = translation * transformation; + } + + return transformation; + } +//----------------------------------------------------------------------------- +#ifdef MSHR_ENABLE_EXPERIMENTAL + bool polyline_is_degenerate(const std::vector& polyline, double tol) + { + double length = 0; + std::vector::const_iterator it = polyline.begin(); + Exact_Point_3 prev = *it; + it++; + for (;it != polyline.end(); it++) + { + length += CGAL::to_double((*it-prev).squared_length()); + } + length += CGAL::to_double((polyline.back()-polyline.front()).squared_length()); + + return length < tol; + } + + + typedef CGAL::Polyhedron_corefinement CGALCSGOperator; + + void convertSubTree(const mshr::CSGGeometry* geometry, Exact_Polyhedron_3& P) + { + switch (geometry->getType()) + { + case mshr::CSGGeometry::Union : + { + const mshr::CSGUnion* u = dynamic_cast(geometry); + dolfin_assert(u); + convertSubTree(u->_g0.get(), P); + Exact_Polyhedron_3 P2; + convertSubTree(u->_g1.get(), P2); + + std::list > intersection_polylines; + CGALCSGOperator op; + op(P, P2, std::back_inserter(intersection_polylines), CGALCSGOperator::Join_tag); + + // Check that intersection is not degenerate + for (std::list >::iterator it=intersection_polylines.begin(); + it != intersection_polylines.end(); it++) + { + if (polyline_is_degenerate(*it, DOLFIN_EPS)) + { + dolfin::dolfin_error("CSGCGALDomain3D.cpp", + "union of csg geometries", + "degenerate intersection polyline (geometries meet in a single point?)"); + } + } + break; + } + case mshr::CSGGeometry::Intersection : + { + const mshr::CSGIntersection* u = dynamic_cast(geometry); + dolfin_assert(u); + convertSubTree(u->_g0.get(), P); + Exact_Polyhedron_3 P2; + convertSubTree(u->_g1.get(), P2); + + std::list > intersection_polylines; + CGALCSGOperator op; + op(P, P2, std::back_inserter(intersection_polylines), CGALCSGOperator::Intersection_tag); + + // Check that intersection is not degenerate + for (std::list >::iterator it=intersection_polylines.begin(); + it != intersection_polylines.end(); it++) + { + if (polyline_is_degenerate(*it, DOLFIN_EPS)) + { + dolfin::dolfin_error("CSGCGALDomain3D.cpp", + "intersection of csg geometries", + "degenerate intersection polyline (geometries meet in a single point?)"); + } + } + + break; + } + case mshr::CSGGeometry::Difference : + { + std::cout << "Difference operator" << std::endl; + const mshr::CSGDifference* u = dynamic_cast(geometry); + dolfin_assert(u); + convertSubTree(u->_g0.get(), P); + Exact_Polyhedron_3 P2; + convertSubTree(u->_g1.get(), P2); + + std::list > intersection_polylines; + CGALCSGOperator op; + op(P, P2, std::back_inserter(intersection_polylines), CGALCSGOperator::P_minus_Q_tag); + + std::cout << "Checking polyline" << std::endl; + // Check that intersection is not degenerate + for (std::list >::iterator it=intersection_polylines.begin(); + it != intersection_polylines.end(); it++) + { + if (polyline_is_degenerate(*it, DOLFIN_EPS)) + { + dolfin::dolfin_error("CSGCGALDomain3D.cpp", + "difference of csg geometries", + "degenerate intersection polyline (geometries meet in a single point?)"); + } + } + + break; + } + case mshr::CSGGeometry::Translation : + { + const mshr::CSGTranslation* t = dynamic_cast(geometry); + dolfin_assert(t); + convertSubTree(t->g.get(), P); + Aff_transformation_3 translation(CGAL::TRANSLATION, Vector_3(t->t.x(), t->t.y(), t->t.z())); + std::transform(P.points_begin(), P.points_end(), P.points_begin(), translation); + break; + } + case mshr::CSGGeometry::Scaling : + { + const mshr::CSGScaling* t = dynamic_cast(geometry); + dolfin_assert(t); + convertSubTree(t->g.get(), P); + + Aff_transformation_3 scaling = get_scaling(*t); + std::transform(P.points_begin(), P.points_end(), P.points_begin(), scaling); + break; + } + case mshr::CSGGeometry::Rotation : + { + const mshr::CSGRotation* t = dynamic_cast(geometry); + dolfin_assert(t); + + convertSubTree(t->g.get(), P); + Aff_transformation_3 rotation = get_rotation(*t); + std::transform(P.points_begin(), P.points_end(), P.points_begin(), rotation); + break; + } + case mshr::CSGGeometry::Cylinder : + { + const mshr::Cylinder* c = dynamic_cast(geometry); + dolfin_assert(c); + make_cylinder(c, P); + break; + } + case mshr::CSGGeometry::Sphere : + { + const mshr::Sphere* s = dynamic_cast(geometry); + dolfin_assert(s); + make_sphere(s, P); + break; + } + case mshr::CSGGeometry::Box : + { + const mshr::Box* b = dynamic_cast(geometry); + dolfin_assert(b); + make_box(b, P); + break; + } + case mshr::CSGGeometry::Tetrahedron : + { + const mshr::Tetrahedron* b = dynamic_cast(geometry); + dolfin_assert(b); + make_tetrahedron(b, P); + break; + } + case mshr::CSGGeometry::Ellipsoid : + { + const mshr::Ellipsoid* b = dynamic_cast(geometry); + dolfin_assert(b); + make_ellipsoid(b, P); + break; + } + case mshr::CSGGeometry::Surface3D : + { + const mshr::Surface3D* b = dynamic_cast(geometry); + dolfin_assert(b); + make_surface3D(b, P); + break; + } + case mshr::CSGGeometry::TriPolyhedron : + { + const mshr::CSGCGALDomain3D* b = dynamic_cast(geometry); + dolfin_assert(b); + Insert_polyhedron_to inserter(b->impl->p); + P.delegate(inserter); + dolfin_assert(P.is_valid()); + break; + } + case mshr::CSGGeometry::Extrude2D : + { + const mshr::Extrude2D* e = dynamic_cast(geometry); + dolfin_assert(e); + make_extrude2D(e, P); + break; + } + default: + dolfin::dolfin_error("CSGCGALDomain.cpp", + "converting geometry to cgal polyhedron", + "Unhandled primitive type"); + } + } +#else + std::shared_ptr + convertSubTree(const mshr::CSGGeometry *geometry) + { + switch (geometry->getType()) + { + case mshr::CSGGeometry::Union : + { + const mshr::CSGUnion* u = dynamic_cast(geometry); + dolfin_assert(u); + std::shared_ptr g0 = convertSubTree(u->_g0.get()); + std::shared_ptr g1 = convertSubTree(u->_g1.get()); + (*g0) += (*g1); + return g0; + + break; + } + case mshr::CSGGeometry::Intersection : + { + const mshr::CSGIntersection* u = dynamic_cast(geometry); + dolfin_assert(u); + std::shared_ptr g0 = convertSubTree(u->_g0.get()); + std::shared_ptr g1 = convertSubTree(u->_g1.get()); + (*g0) *= (*g1); + return g0; + break; + } + case mshr::CSGGeometry::Difference : + { + const mshr::CSGDifference* u = dynamic_cast(geometry); + dolfin_assert(u); + std::shared_ptr g0 = convertSubTree(u->_g0.get()); + std::shared_ptr g1 = convertSubTree(u->_g1.get()); + (*g0) -= (*g1); + return g0; + break; + } + case mshr::CSGGeometry::Translation : + { + const mshr::CSGTranslation* t = dynamic_cast(geometry); + dolfin_assert(t); + std::shared_ptr g = convertSubTree(t->g.get()); + Aff_transformation_3 translation(CGAL::TRANSLATION, Vector_3(t->t.x(), t->t.y(), t->t.z())); + g->transform(translation); + return g; + break; + } + case mshr::CSGGeometry::Scaling : + { + const mshr::CSGScaling* t = dynamic_cast(geometry); + dolfin_assert(t); + std::shared_ptr g = convertSubTree(t->g.get()); + Aff_transformation_3 scaling = get_scaling(*t); + g->transform(scaling); + return g; + break; + } + case mshr::CSGGeometry::Rotation : + { + const mshr::CSGRotation* t = dynamic_cast(geometry); + dolfin_assert(t); + + std::shared_ptr g = convertSubTree(t->g.get()); + Aff_transformation_3 rotation = get_rotation(*t); + g->transform(rotation); + return g; + break; + } + case mshr::CSGGeometry::Cylinder : + { + const mshr::Cylinder* c = dynamic_cast(geometry); + dolfin_assert(c); + Exact_Polyhedron_3 P; + make_cylinder(c, P); + return std::shared_ptr(new Nef_polyhedron_3(P)); + break; + } + case mshr::CSGGeometry::Sphere : + { + const mshr::Sphere* s = dynamic_cast(geometry); + dolfin_assert(s); + Exact_Polyhedron_3 P; + make_sphere(s, P); + return std::shared_ptr(new Nef_polyhedron_3(P)); + break; + } + case mshr::CSGGeometry::Box : + { + const mshr::Box* b = dynamic_cast(geometry); + dolfin_assert(b); + Exact_Polyhedron_3 P; + make_box(b, P); + return std::shared_ptr(new Nef_polyhedron_3(P)); + break; + } + case mshr::CSGGeometry::Tetrahedron : + { + const mshr::Tetrahedron* b = dynamic_cast(geometry); + dolfin_assert(b); + Exact_Polyhedron_3 P; + make_tetrahedron(b, P); + return std::shared_ptr(new Nef_polyhedron_3(P)); + break; + } + case mshr::CSGGeometry::Ellipsoid : + { + const mshr::Ellipsoid* b = dynamic_cast(geometry); + dolfin_assert(b); + Exact_Polyhedron_3 P; + make_ellipsoid(b, P); + return std::shared_ptr(new Nef_polyhedron_3(P)); + break; + } + case mshr::CSGGeometry::Surface3D : + { + const mshr::Surface3D* b = dynamic_cast(geometry); + dolfin_assert(b); + Exact_Polyhedron_3 P; + make_surface3D(b, P); + return std::shared_ptr(new Nef_polyhedron_3(P)); + break; + } + case mshr::CSGGeometry::TriPolyhedron : + { + const mshr::CSGCGALDomain3D* b = dynamic_cast(geometry); + dolfin_assert(b); + return std::shared_ptr(new Nef_polyhedron_3(b->impl->p)); + break; + } + case mshr::CSGGeometry::Extrude2D : + { + const mshr::Extrude2D* e = dynamic_cast(geometry); + dolfin_assert(e); + Exact_Polyhedron_3 P; + make_extrude2D(e, P); + return std::shared_ptr(new Nef_polyhedron_3(P)); + break; + } + default: + dolfin::dolfin_error("CSGCGALDomain.cpp", + "converting geometry to cgal polyhedron", + "Unhandled primitive type"); + } + // Make compiler happy. + return std::shared_ptr(new Nef_polyhedron_3); + } +#endif +//----------------------------------------------------------------------------- + void convert(const mshr::CSGGeometry& geometry, + Exact_Polyhedron_3 &P) + { + // If the tree has only one node, we don't have to convert to Nef + // polyhedrons for csg manipulations + if (!geometry.is_operator()) + { + switch (geometry.getType()) + { + + case mshr::CSGGeometry::Cylinder : + { + const mshr::Cylinder* c = dynamic_cast(&geometry); + dolfin_assert(c); + make_cylinder(c, P); + break; + } + case mshr::CSGGeometry::Sphere : + { + const mshr::Sphere* s = dynamic_cast(&geometry); + dolfin_assert(s); + make_sphere(s, P); + break; + } + case mshr::CSGGeometry::Box : + { + const mshr::Box* b = dynamic_cast(&geometry); + dolfin_assert(b); + make_box(b, P); + break; + } + + case mshr::CSGGeometry::Tetrahedron : + { + const mshr::Tetrahedron* b = dynamic_cast(&geometry); + dolfin_assert(b); + make_tetrahedron(b, P); + break; + } + case mshr::CSGGeometry::Ellipsoid : + { + const mshr::Ellipsoid* b = dynamic_cast(&geometry); + dolfin_assert(b); + make_ellipsoid(b, P); + break; + } + case mshr::CSGGeometry::TriPolyhedron : + { + const mshr::CSGCGALDomain3D* p = dynamic_cast(&geometry); + dolfin_assert(p); + Insert_polyhedron_to inserter(p->impl->p); + P.delegate(inserter); + dolfin_assert(P.is_valid()); + break; + } + case mshr::CSGGeometry::Surface3D : + { + const mshr::Surface3D* b = dynamic_cast(&geometry); + dolfin_assert(b); + make_surface3D(b, P); + break; + } + case mshr::CSGGeometry::Extrude2D : + { + const mshr::Extrude2D* e = dynamic_cast(&geometry); + dolfin_assert(e); + make_extrude2D(e, P); + break; + } + + default: + dolfin::dolfin_error("CSGCGALDomain3D.cpp", + "converting geometry to cgal polyhedron", + "Unhandled primitive type"); + } + } + else + { +#ifdef MSHR_ENABLE_EXPERIMENTAL + convertSubTree(&geometry, P); +#else + log(dolfin::TRACE, "Convert to nef polyhedron"); + std::shared_ptr cgal_geometry + = convertSubTree(&geometry); + dolfin_assert(cgal_geometry->is_valid()); + dolfin_assert(cgal_geometry->is_simple()); + cgal_geometry->convert_to_polyhedron(P); +#endif + } + + if (P.size_of_facets() == 0) + { + dolfin::dolfin_error("CSGCGALDomain3D.cpp", + "Convert geometry to polyhedron", + "Geometry contains no facet"); + } + + log(dolfin::TRACE, "Number of vertices: %d", P.size_of_vertices()); + log(dolfin::TRACE, "Number of facets: %d", P.size_of_facets()); + } + } //end unnamed namespace + + + namespace mshr + { + //----------------------------------------------------------------------------- + struct CSGCGALDomain3DQueryStructureImpl + { + template + CSGCGALDomain3DQueryStructureImpl(A start, A end, const Exact_Polyhedron_3& p) + : aabb_tree(start, end, p){} + + AABB_Tree aabb_tree; + }; + //----------------------------------------------------------------------------- + CSGCGALDomain3DQueryStructure::CSGCGALDomain3DQueryStructure(std::unique_ptr impl) + : impl(std::move(impl)) + { + // Do nothing + } + //----------------------------------------------------------------------------- + CSGCGALDomain3DQueryStructure::~CSGCGALDomain3DQueryStructure() + { + // Do nothing + } + //----------------------------------------------------------------------------- + CSGCGALDomain3D::CSGCGALDomain3D() : impl(new CSGCGALDomain3DImpl) + { + parameters = default_parameters(); + } + //----------------------------------------------------------------------------- + CSGCGALDomain3D::CSGCGALDomain3D(std::shared_ptr csg) + : impl(new CSGCGALDomain3DImpl) + { + parameters = default_parameters(); + + if (csg->dim() != 3) + { + dolfin::dolfin_error("CSGCGALDomain3D.cpp", + "Creating polyhedral domain", + "Geometry has dimension %d, expected 3", csg->dim()); + } + + convert(*csg, impl->p); + + } + //----------------------------------------------------------------------------- + CSGCGALDomain3D::CSGCGALDomain3D(const std::vector>& vertices, + const std::vector>& facets) + : impl(new CSGCGALDomain3DImpl) + { + parameters = default_parameters(); + + // Create the polyhedron + BuildFromFacetList builder(vertices, facets, std::set{}); + impl->p.delegate(builder); + } + //----------------------------------------------------------------------------- + CSGCGALDomain3D::~CSGCGALDomain3D(){} + //----------------------------------------------------------------------------- + void CSGCGALDomain3D::insert(const CSGCGALDomain3D& p) + { + Insert_polyhedron_to inserter(p.impl->p); + impl->p.delegate(inserter); + CGAL_assertion(impl->p.is_valid()); + } +//----------------------------------------------------------------------------- + std::size_t CSGCGALDomain3D::num_vertices() const + { return impl->p.size_of_vertices(); } +//----------------------------------------------------------------------------- + std::size_t CSGCGALDomain3D::num_facets() const + { return impl->p.size_of_facets(); } +//----------------------------------------------------------------------------- + std::size_t CSGCGALDomain3D::num_halfedges() const + { return impl->p.size_of_halfedges(); } +//----------------------------------------------------------------------------- + double CSGCGALDomain3D::volume() const + { + double volume = .0; + for (Exact_Polyhedron_3::Facet_const_iterator it = impl->p.facets_begin(); + it != impl->p.facets_end(); it++) + { + const Exact_Polyhedron_3::Halfedge_const_handle h = it->halfedge(); + const Vector_3 V0 = h->vertex()->point()-CGAL::ORIGIN; + const Vector_3 V1 = h->next()->vertex()->point()-CGAL::ORIGIN; + const Vector_3 V2 = h->next()->next()->vertex()->point()-CGAL::ORIGIN; + + volume += CGAL::to_double(V0*CGAL::cross_product(V1, V2)); + } + + return volume/6.0; + } +//----------------------------------------------------------------------------- + std::unique_ptr> CSGCGALDomain3D::get_vertices() const + { + typedef typename Exact_Polyhedron_3::Vertex_const_iterator Vertex_const_iterator; + + std::unique_ptr> vertices(new std::vector()); + vertices->reserve(impl->p.size_of_vertices()); + + for(Vertex_const_iterator + vi = impl->p.vertices_begin(), end = impl->p.vertices_end(); + vi != end ; ++vi) + { + vertices->push_back(::CGAL::to_double( vi->point().x())); + vertices->push_back(::CGAL::to_double( vi->point().y())); + vertices->push_back(::CGAL::to_double( vi->point().z())); + } + + return vertices; + } +//----------------------------------------------------------------------------- + std::unique_ptr> CSGCGALDomain3D::get_facets() const + { + typedef typename Exact_Polyhedron_3::Vertex_const_iterator Vertex_const_iterator; + typedef typename Exact_Polyhedron_3::Facet_const_iterator Facet_const_iterator; + typedef typename Exact_Polyhedron_3::Halfedge_around_facet_const_circulator HFCC; + + typedef CGAL::Inverse_index Index; + Index index(impl->p.vertices_begin(), impl->p.vertices_end()); + + std::unique_ptr> facets(new std::vector()); + facets->reserve(impl->p.size_of_facets()); + + for(Facet_const_iterator + fi = impl->p.facets_begin(), end = impl->p.facets_end(); + fi != end; ++fi) + { + HFCC hc = fi->facet_begin(); + facets->push_back(index[hc->vertex()]); + hc++; + facets->push_back(index[hc->vertex()]); + hc++; + facets->push_back(index[hc->vertex()]); + } + + return facets; + } +//----------------------------------------------------------------------------- + void CSGCGALDomain3D::get_points_in_holes(std::vector& holes, + std::shared_ptr q) const + { + std::vector parts; + mshr::PolyhedronUtils::get_disconnected_components(impl->p,std::back_inserter(parts)); + + if (parts.size() > 1) + { + if (!q) + q = get_query_structure(); + + for (std::vector::const_iterator it = parts.begin(); + it != parts.end(); it++) + { + typename Exact_Polyhedron_3::Halfedge_const_handle h = (*it)->halfedge(); + const Exact_Point_3 x1 = h->vertex()->point(); + const Exact_Point_3 x2 = h->next()->vertex()->point(); + const Exact_Point_3 x3 = h->next()->next()->vertex()->point(); + const Exact_Point_3 origin( (x1.x()+x2.x()+x3.x())/3, + (x1.y()+x2.y()+x3.y())/3, + (x1.z()+x2.z()+x3.z())/3); + + Vector_3 n = CGAL::cross_product(x3-x1, x2-x1); + + // shoot a ray from the facet and count the number of hits + Ray_3 ray(origin, n); + + std::list intersections; + q->impl->aabb_tree.all_intersections(ray, std::back_inserter(intersections)); + // std::cout << "Number of intersections: " << intersections.size() << std::endl; + + // Collect unique intersection points not including the origin point + std::set intersection_points; + for (std::list::const_iterator + iit = intersections.begin(); + iit != intersections.end(); iit++) + { + if (const Exact_Point_3* p = CGAL::object_cast(&(iit->first))) + { + if (*p != origin) + intersection_points.insert(*p); + } + } + + // std::cout << "Number of unique point intersections: " << intersection_points.size() << std::endl; + if (intersection_points.size() % 2 == 0) + { + // This is a hole + // Find a point strictly inside it + + std::set::const_iterator pit = intersection_points.begin(); + Exact_Point_3 closest_point = *pit; + Exact_Kernel::FT distance_to_closest = (closest_point-origin).squared_length(); + pit++; + + for (;pit != intersection_points.end(); pit++) + { + Exact_Kernel::FT d = (*pit-origin).squared_length(); + if (d < distance_to_closest) + { + distance_to_closest = d; + closest_point = *pit; + } + } + + const dolfin::Point h_point( (CGAL::to_double(origin.x())+CGAL::to_double(closest_point.x()))/2, + (CGAL::to_double(origin.y())+CGAL::to_double(closest_point.y()))/2, + (CGAL::to_double(origin.z())+CGAL::to_double(closest_point.z()))/2 ); + // std::cout << "Point in hole: " << h_point << std::endl; + holes.push_back(h_point); + } + } + } + } +//----------------------------------------------------------------------------- + void CSGCGALDomain3D::remove_degenerate_facets(double tolerance) + { + remove_degenerate(impl->p, tolerance); + } +//----------------------------------------------------------------------------- + void CSGCGALDomain3D::ensure_meshing_preconditions() + { + if (!impl->p.is_valid()) + dolfin::dolfin_error("CSGCGDomain3D.cpp", + "Checking meshing preconditions", + "Polyhedron is not valid"); + + if (parameters["remove_degenerate"]) + remove_degenerate_facets(parameters["degenerate_tolerance"]); + } +//----------------------------------------------------------------------------- + std::shared_ptr CSGCGALDomain3D::get_query_structure() const + { + std::unique_ptr i(new CSGCGALDomain3DQueryStructureImpl(faces(impl->p).first, + faces(impl->p).second, + impl->p)); + return std::shared_ptr(new CSGCGALDomain3DQueryStructure(std::move(i))); + } +//----------------------------------------------------------------------------- + bool CSGCGALDomain3D::is_insideout() const + { + typedef Exact_Kernel::Ray_3 Exact_Ray_3; + + // Pick a point on a facet a. Shoot in the direction of the normal and count + // the number of distinct hits (if the ray hit an edge, the same intersection + // point will hit sevel facets). Excluding the hit on facet a the number + // should be even for the polyhedron to be bounded. + // TODO: Take QueryStructure argument. + Exact_Polyhedron_3::Facet_iterator f = impl->p.facets_begin(); + + Exact_Ray_3 ray; + + { + Exact_Polyhedron_3::Halfedge_handle h = f->halfedge(); + Exact_Triangle_3 t(h->vertex()->point(), + h->next()->vertex()->point(), + h->next()->next()->vertex()->point()); + f++; + + //Compute the ray + Exact_Vector_3 normal = CGAL::normal(t.vertex(0), t.vertex(1), t.vertex(2)); + Exact_Point_3 mid = CGAL::centroid(t); + + ray = Exact_Ray_3(mid, normal); + } + + // Collect the points to avoid double hits on facets. + std::set points; + for (; f != impl->p.facets_end(); ++f) + { + Exact_Polyhedron_3::Halfedge_handle h = f->halfedge(); + Exact_Triangle_3 t(h->vertex()->point(), + h->next()->vertex()->point(), + h->next()->next()->vertex()->point()); + + + auto result = intersection(t, ray); + if(result) + { + if (const Exact_Point_3* p = boost::get(&*result)) + { + points.insert(*p); + } + } + } + + // std::cout << "Number of intersections: " << points.size() << std::endl; + return points.size() % 2 != 0; + } +//----------------------------------------------------------------------------- + bool CSGCGALDomain3D::is_selfintersecting(bool verbose) const + { + if (verbose) + { + const std::string s = PolyhedronUtils::list_self_intersections(impl->p); + if (s != "") + { + dolfin::cout << s; + return true; + } + else + { + return false; + } + } + else + { + return CGAL::Polygon_mesh_processing::does_self_intersect(impl->p); + } + } +//----------------------------------------------------------------------------- + std::size_t CSGCGALDomain3D::num_degenerate_facets(double threshold) const + { + return number_of_degenerate_facets(impl->p, threshold); + } +//----------------------------------------------------------------------------- + std::pair CSGCGALDomain3D::facet_area_minmax() const + { + double smallest_triangle = std::numeric_limits::max(); + double largest_triangle = 0.; + for (auto f = impl->p.facets_begin(); f != impl->p.facets_end(); f++) + { + auto h = f->halfedge(); + if (f->is_triangle()) + { + const Exact_Triangle_3 t(h->vertex()->point(), + h->next()->vertex()->point(), + h->next()->next()->vertex()->point()); + const double squared_area = CGAL::to_double(t.squared_area()); + smallest_triangle = std::min(smallest_triangle, + squared_area); + largest_triangle = std::max(largest_triangle, + squared_area); + } + } + return std::make_pair(sqrt(smallest_triangle), + sqrt(largest_triangle)); + } + //----------------------------------------------------------------------------- + void CSGCGALDomain3D::save_off(std::string filename) const + { + { + std::string message = "Writing to file: "+filename; + log(dolfin::TRACE, message); + } + + std::ofstream outfile(filename.c_str()); + outfile << std::setprecision(16); + + outfile << impl->p; + + // std::map vertex_map; + // std::size_t vertex_counter = 0; + + // // Write header + // outfile << "OFF " << std::endl << impl->p.size_of_vertices() << " " << impl->p.size_of_facets() << " 0" << std::endl << std::endl; + + // // Write vertices + // for (Exact_Polyhedron_3::Vertex_const_iterator vit = impl->p.vertices_begin(); vit != impl->p.vertices_end(); vit++) + // { + // vertex_map[vit] = vertex_counter; + // outfile << std::fixed << std::setprecision(16) << CGAL::to_double(vit->point().x()) << " " + // << CGAL::to_double(vit->point().y()) << " " + // << CGAL::to_double(vit->point().z()) << std::endl; + // vertex_counter++; + // } + + // for (Exact_Polyhedron_3::Facet_const_iterator fit = impl->p.facets_begin(); fit != impl->p.facets_end(); fit++) + // { + // Exact_Polyhedron_3::Halfedge_const_iterator h = fit->halfedge(); + // outfile << "3"; + // outfile << vertex_map[h->vertex()] << " " + // << vertex_map[h->next()->vertex()] << " " + // << vertex_map[h->next()->next()->vertex()] << std::endl; + // } + + outfile.close(); + } +//----------------------------------------------------------------------------- +void CSGCGALDomain3D::save(std::string filename) const + { + boost::filesystem::path fpath(filename); + if (fpath.extension() == ".off") + { + save_off(filename); + } + else if (fpath.extension() == ".asc") + { + std::vector> new_vertices; + std::vector> new_facets; + std::vector vertices = *get_vertices(); + std::vector facets = *get_facets(); + for (std::size_t i = 0; i < vertices.size(); i += 3) + { + std::array vertex; + vertex[0] = vertices[i]; + vertex[1] =vertices[i+1]; + vertex[2] = vertices[i+2]; + new_vertices.push_back(vertex); + } + for (std::size_t i = 0; i < facets.size(); i += 3) + { + std::array facet; + facet[0] = facets[i]; + facet[1] = facets[i+1]; + facet[2] = facets[i+2]; + new_facets.push_back(facet); + } + mshr::ASCFileReader::write(filename,new_vertices,new_facets); + } + else if (fpath.extension() == ".stl") + { + std::vector> new_vertices; + std::vector> new_facets; + std::vector vertices = *get_vertices(); + std::vector facets = *get_facets(); + for (std::size_t i = 0; i < vertices.size(); i += 3) + { + std::array vertex; + vertex[0] = vertices[i]; + vertex[1] =vertices[i+1]; + vertex[2] = vertices[i+2]; + new_vertices.push_back(vertex); + } + for (std::size_t i = 0; i < facets.size(); i += 3) + { + std::array facet; + facet[0] = facets[i]; + facet[1] = facets[i+1]; + facet[2] = facets[i+2]; + new_facets.push_back(facet); + } + mshr::STLFileReader::write(filename,new_vertices,new_facets); + } + else + { + dolfin::dolfin_error("CSGCGALDomain3D", + "extension to file is not known", + "Failed write file"); + } + } +//----------------------------------------------------------------------------- + std::pair CSGCGALDomain3D::edge_length_range() const + { + Exact_Polyhedron_3::Edge_const_iterator it = impl->p.edges_begin(); + double shortest = CGAL::to_double((it->vertex()->point() - it->opposite()->vertex()->point()).squared_length()); + double longest = shortest; + it++; + + for (; it != impl->p.edges_end(); it++) + { + const double l = CGAL::to_double((it->vertex()->point() - it->opposite()->vertex()->point()).squared_length()); + shortest = std::min(shortest, l); + longest = std::max(longest, l); + } + + return std::make_pair(sqrt(shortest), sqrt(longest)); + } +//----------------------------------------------------------------------------- + std::size_t CSGCGALDomain3D::num_short_edges(double tolerance) const + { + std::size_t count = 0; + for (Exact_Polyhedron_3::Edge_const_iterator it = impl->p.edges_begin(); it != impl->p.edges_end(); it++) + { + const Exact_Kernel::FT l = (it->vertex()->point() - it->opposite()->vertex()->point()).squared_length(); + if (l < tolerance) + count++; + } + + return count; + } +//----------------------------------------------------------------------------- + std::string CSGCGALDomain3D::str(bool verbose) const + { + std::stringstream ss; + ss << "Triangular polyhedron with" << std::endl; + ss << " " << num_vertices() << " vertices," << std::endl; + ss << " " << num_facets() << " facets," << std::endl; + ss << " " << num_halfedges() << " halfedges." << std::endl; + + if (verbose) + { + ss << "Volume: " << volume() << std::endl; + const std::pair edge_lengths = edge_length_range(); + ss << "Edge length range: (" << edge_lengths.first << ", " << edge_lengths.second << ")" << std::endl; + ss << "Degenerate facets: " << num_degenerate_facets(1e-12) << std::endl; + ss << "Is inside out: " << (is_insideout() ? "Yes" : "No") << std::endl; + ss << "Is self-intersecting: " << (is_selfintersecting() ? "Yes" : "No") << std::endl; + ss << "Is closed: " << (num_holes() > 0 ? "Yes" : "No"); + } + + return ss.str(); + } +//----------------------------------------------------------------------------- + std::shared_ptr CSGCGALDomain3D::convex_hull() const + { + std::shared_ptr res(new CSGCGALDomain3D); + CGAL::convex_hull_3(impl->p.points_begin(), impl->p.points_end(), res->impl->p); + + return res; + } + //----------------------------------------------------------------------------- + std::shared_ptr CSGCGALDomain3D::convex_hull(const std::vector>& point_set) + { + std::vector points; + points.reserve(point_set.size()); + for (const std::array& p : point_set) + points.push_back(Exact_Point_3(p[0], p[1], p[2])); + + std::shared_ptr res(new CSGCGALDomain3D); + CGAL::convex_hull_3(points.begin(), + points.end(), + res->impl->p); + + return res; + } +//----------------------------------------------------------------------------- + void CSGCGALDomain3D::filter_facets(dolfin::Point start, + double threshold, + std::shared_ptr q) + { + std::cout << "Filtering facets" << std::endl; + + if (!q) + q = get_query_structure(); + + typedef AABB_Tree::Point_and_primitive_id Point_and_primitive_id; + typedef Exact_Polyhedron_3::Face_handle Face_handle; + typedef Exact_Polyhedron_3::Halfedge_handle Halfedge_handle; + Exact_Point_3 query_point(start[0], start[1], start[2]); + Point_and_primitive_id pp = q->impl->aabb_tree.closest_point_and_primitive(query_point); + dolfin::log(dolfin::TRACE, "Closest point: (%f, %f, %f)", + CGAL::to_double(pp.first.x()), + CGAL::to_double(pp.first.y()), + CGAL::to_double(pp.first.z())); + + const double cos_threshold = cos(threshold); + + std::set to_be_removed; + { + std::deque queue; + queue.push_back(pp.second); + + while (!queue.empty()) + { + Face_handle f = queue.front(); + queue.pop_front(); + + if (to_be_removed.count(f) > 0) + continue; + + to_be_removed.insert(f); + + const Halfedge_handle start = f->halfedge(); + Halfedge_handle c = start; + const Exact_Triangle_3 f_triangle = PolyhedronUtils::get_facet_triangle(f->halfedge()); + do + { + Halfedge_handle current = c; + c = c->next(); + + if (current->is_border_edge()) + continue; + + Face_handle f2 = current->opposite()->facet(); + if (PolyhedronUtils::get_triangle_cos_angle(f_triangle, + PolyhedronUtils::get_facet_triangle(f2->halfedge())) > cos_threshold && + to_be_removed.count(f2) == 0) + { + queue.push_back(f2); + } + + } while (c != start); + } + } + + dolfin::log(dolfin::TRACE, "Removing %d facets", to_be_removed.size()); + + for (auto fit = to_be_removed.begin(); fit != to_be_removed.end(); fit++) + { + impl->p.erase_facet((*fit)->halfedge()); + } + + dolfin_assert(impl->p.is_valid()); + } +//----------------------------------------------------------------------------- + // std::shared_ptr get_component(dolfin::Point p, + // std::shared_ptr qs) + // { + // if (!qs) + // qs = get_query_structure(); + + + // typedef AABB_Tree::Point_and_primitive_id Point_and_primitive_id; + // typedef Exact_Polyhedron_3::Face_handle Face_handle; + // typedef Exact_Polyhedron_3::Halfedge_handle Halfedge_handle; + // Exact_Point_3 query_point(start[0], start[1], start[2]); + // Point_and_primitive_id pp = q->impl->aabb_tree.closest_point_and_primitive(query_point); + // dolfin::log(dolfin::TRACE, "Closest point: %s", dolfin::Point(CGAL::double(pp.first[0])), + // dolfin::Point(CGAL::to_double(pp.first[1])), + // dolfin::Point(CGAL::to_double(pp.first[2]))); + + // const double cos_threshold = cos(threshold); + + // std::set to_be_removed; + // { + // std::deque queue; + // queue.push_back(pp.second); + + // while (!queue.empty()) + // { + // Face_handle f = queue.front(); + // queue.pop_front(); + + // if (to_be_removed.count(f) > 0) + // continue; + + // to_be_removed.insert(f); + + // const Halfedge_handle start = f->halfedge(); + // Halfedge_handle c = start; + // const Exact_Triangle_3 f_triangle = PolyhedronUtils::get_facet_triangle(f->halfedge()); + // do + // { + // Halfedge_handle current = c; + // c = c->next(); + + // if (current->is_border_edge()) + // continue; + + // Face_handle f2 = current->opposite()->facet(); + // if (PolyhedronUtils::get_triangle_cos_angle(f_triangle, + // PolyhedronUtils::get_facet_triangle(f2->halfedge())) > cos_threshold && + // to_be_removed.count(f2) == 0) + // { + // queue.push_back(f2); + // } + + // } while (c != start); + // } + // } + + // std::cout << "Removing " << to_be_removed.size() << " facets" << std::endl; + + // for (auto fit = to_be_removed.begin(); fit != to_be_removed.end(); fit++) + // { + // impl->p.erase_facet((*fit)->halfedge()); + // } + + // dolfin_assert(impl->p.is_valid()); + + // } +//----------------------------------------------------------------------------- + void CSGCGALDomain3D::inside_out() + { + dolfin_assert(impl->p.is_valid()); + impl->p.inside_out(); + } +//----------------------------------------------------------------------------- + std::size_t CSGCGALDomain3D::num_holes() const + { + impl->p.normalize_border(); + + dolfin_assert(impl->p.is_valid(false, 0)); + + const std::vector holes = PolyhedronUtils::get_holes(impl->p); + return holes.size(); + } + //----------------------------------------------------------------------------- + bool CSGCGALDomain3D::close_hole(std::size_t hole, std::string method) + { + dolfin::warning("Hole closing is an experimental feature"); + dolfin_assert(impl->p.is_valid(false, 0)); + impl->p.normalize_border(); + + const std::vector holes = PolyhedronUtils::get_holes(impl->p); + + if (method == "auto") + PolyhedronUtils::close_hole(impl->p, + holes[hole]); + else if (method == "planar") + { + const bool res = PolyhedronUtils::triangulate_polygon_3d(impl->p, + holes[hole], + false); + + dolfin_assert(impl->p.is_valid(false, 0)); + //dolfin_assert(impl->p.is_pure_triangle()); + impl->p.normalize_border(); + return res; + } + else if (method == "split") + { + const bool res = PolyhedronUtils::split_hole_planar(impl->p, holes[hole]); + dolfin_assert(impl->p.is_valid(false, 0)); + //dolfin_assert(impl->p.is_pure_triangle()); + impl->p.normalize_border(); + return res; + } + else + { + dolfin::dolfin_error("CSGCGALDomain3D.cpp", + "triangulate hole", + "Unknown hole closing method"); + return false; + } + return true; + } + //----------------------------------------------------------------------------- + void CSGCGALDomain3D::list_hole(std::size_t hole) const + { + const std::vector holes = PolyhedronUtils::get_holes(impl->p); + PolyhedronUtils::list_hole(holes[hole]); + } + //----------------------------------------------------------------------------- + void CSGCGALDomain3D::close_holes() + { + dolfin::warning("Hole closing is an experimental feature"); + + const std::vector holes = PolyhedronUtils::get_holes(impl->p); + + std::size_t counter = 0; + for (const Exact_Polyhedron_3::Halfedge_handle h : holes) + { + // save_off("not_intersecting.off"); + PolyhedronUtils::close_hole(impl->p, h); + + counter++; + + dolfin_assert(impl->p.is_valid(false, 0)); + dolfin_assert(impl->p.is_pure_triangle()); + } + + dolfin_assert(impl->p.is_closed()); + } + //----------------------------------------------------------------------------- + std::shared_ptr CSGCGALDomain3D::reconstruct_surface(double expansion) const + { + std::vector> new_vertices; + std::vector> new_facets; + + { + std::unique_ptr> vertices = get_vertices(); + std::unique_ptr> facets = get_facets(); + + SurfaceReconstruction::reconstruct(*vertices, + *facets, + new_vertices, + new_facets, + expansion); + } + + return std::shared_ptr(new CSGCGALDomain3D(new_vertices, + new_facets)); + } + //----------------------------------------------------------------------------- + std::shared_ptr CSGCGALDomain3D::remesh_surface(double edge_length, + double sharp_edge_tolerance) const + { + std::vector> remeshed_vertices; + std::vector> remeshed_facets; + + std::unique_ptr> vertices = get_vertices(); + std::unique_ptr> facets = get_facets(); + + SurfaceReconstruction::remesh(edge_length, + sharp_edge_tolerance, + *vertices, + *facets, + remeshed_vertices, + remeshed_facets); + return std::shared_ptr(new CSGCGALDomain3D(remeshed_vertices, + remeshed_facets)); + } + //----------------------------------------------------------------------------- + std::size_t CSGCGALDomain3D::remove_selfintersections() + { + return PolyhedronUtils::remove_self_intersections(impl->p); + } + //----------------------------------------------------------------------------- + void CSGCGALDomain3D::normalize() + { + typedef typename Exact_Polyhedron_3::Vertex_iterator Vertex_iterator; + typedef typename Exact_Kernel::Aff_transformation_3 Aff_transformation_3; + + if (impl->p.size_of_vertices() > 0) + { + Vertex_iterator v = impl->p.vertices_begin(); + const Vertex_iterator end = impl->p.vertices_end(); + CGAL::Bbox_3 bbox = v->point().bbox(); + v++; + + for (; v != end; v++) + { + bbox += v->point().bbox(); + } + + std::cout << "Bounding box: " << bbox << std::endl; + const Exact_Point_3 mid((bbox.xmax()+bbox.xmin())/2, + (bbox.ymax()+bbox.ymin())/2, + (bbox.zmax()+bbox.zmin())/2); + std::cout << "Mid point: " << mid << std::endl; + const Exact_Vector_3 translation = CGAL::ORIGIN-mid; + const Exact_Kernel::RT scaling = std::max(std::max(bbox.xmax()-bbox.xmin(), + bbox.ymax()-bbox.ymin()), + bbox.zmax()-bbox.zmin()); + + std::cout << "Scaling: " << scaling << std::endl; + + const Aff_transformation_3 t(CGAL::Translation(), translation); + const Aff_transformation_3 s(CGAL::Scaling(), 2/scaling); + const Aff_transformation_3 transform = s*t; + + CGAL::Bbox_3 new_bbox = Exact_Point_3(0,0,0).bbox(); + + for (Vertex_iterator v = impl->p.vertices_begin(); v != end; v++) + { + //v->point() = v->point() + translation; + v->point() = transform.transform(v->point()); + new_bbox += v->point().bbox(); + } + + std::cout << "New bbox: " << new_bbox << std::endl; + } + } + //----------------------------------------------------------------------------- + void CSGCGALDomain3D::smooth_taubin(std::size_t iterations) + { + for (std::size_t i = 0; i < iterations; i++) + { + LaplacianSmoothing::smooth(impl->p, .8); + LaplacianSmoothing::smooth(impl->p, -.805); + } + } + //----------------------------------------------------------------------------- + void CSGCGALDomain3D::smooth_laplacian(double c) + { + LaplacianSmoothing::smooth(impl->p, c); + } + //----------------------------------------------------------------------------- + double CSGCGALDomain3D::sharpest_edge() const + { + return PolyhedronUtils::sharpest_edge(impl->p); + } + //----------------------------------------------------------------------------- + std::size_t CSGCGALDomain3D::num_sharp_edges(double tolerance) const + { + return 0; + } + //----------------------------------------------------------------------------- + std::size_t CSGCGALDomain3D::num_disconnected_components() const + { + std::vector parts; + mshr::PolyhedronUtils::get_disconnected_components(impl->p, std::back_inserter(parts)); + return parts.size(); + } + //----------------------------------------------------------------------------- + std::size_t CSGCGALDomain3D::keep_largest_components(std::size_t n) + { + return impl->p.keep_largest_connected_components(static_cast(n)); + } + //----------------------------------------------------------------------------- + std::pair CSGCGALDomain3D::get_aabb() const + { + std::pair aabb = PolyhedronUtils::get_aabb(impl->p); + auto res = std::make_pair(dolfin::Point(CGAL::to_double(aabb.first[0]), + CGAL::to_double(aabb.first[1]), + CGAL::to_double(aabb.first[2])), + dolfin::Point(CGAL::to_double(aabb.second[0]), + CGAL::to_double(aabb.second[1]), + CGAL::to_double(aabb.second[2]))); + return res; + } + //----------------------------------------------------------------------------- + void CSGCGALDomain3D::refine_center_vertex() + { + PolyhedronUtils::center_vertex_refinement(impl->p); + } + //----------------------------------------------------------------------------- + void CSGCGALDomain3D::refine_edge_split() + { + PolyhedronUtils::split_edge_refinement(impl->p); + } + //----------------------------------------------------------------------------- +} // end namespace mshr + diff --git a/src/CSGCGALMeshGenerator2D.cpp b/src/CSGCGALMeshGenerator2D.cpp new file mode 100644 index 0000000..6851520 --- /dev/null +++ b/src/CSGCGALMeshGenerator2D.cpp @@ -0,0 +1,408 @@ +// Copyright (C) 2012 Johannes Ring, 2012-2017 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + + +#include +#include +#include +#include + +#ifndef CGAL_HEADER_ONLY +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +typedef CGAL::Exact_predicates_inexact_constructions_kernel Inexact_Kernel; +typedef CGAL::Delaunay_mesh_vertex_base_2 Vertex_base; +typedef CGAL::Delaunay_mesh_face_base_2 Face_base; +typedef CGAL::Triangulation_data_structure_2 TDS; +typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; +typedef CGAL::Delaunay_mesh_size_criteria_2 Mesh_criteria_2; +typedef CGAL::Delaunay_mesher_2 CGAL_Mesher_2; + +typedef CDT::Vertex_handle Vertex_handle; +typedef CDT::Face_handle Face_handle; +typedef Inexact_Kernel::Point_2 Point_2; + +namespace mshr +{ + +//----------------------------------------------------------------------------- +CSGCGALMeshGenerator2D::CSGCGALMeshGenerator2D() +{ + parameters = default_parameters(); +} +//----------------------------------------------------------------------------- +CSGCGALMeshGenerator2D::~CSGCGALMeshGenerator2D() {} +//----------------------------------------------------------------------------- +void explore_subdomain(const CDT& cdt, + Face_handle f, + std::size_t marker, + std::map& subdomain_map, + std::set& not_visited) +{ + std::deque queue; + queue.push_back(f); + + while (!queue.empty()) + { + CDT::Face_handle face = queue.front(); + queue.pop_front(); + + std::set::iterator face_not_visited = not_visited.find(face); + if (face_not_visited != not_visited.end()) + { + dolfin_assert(subdomain_map.find(face) == subdomain_map.end()); + not_visited.erase(face_not_visited); + subdomain_map[face] = marker; + + for(int i = 0; i < 3; i++) + { + const CDT::Edge e(face,i); + if (!cdt.is_constrained(e)) + queue.push_back(face->neighbor(i)); + } + } + } +} +//----------------------------------------------------------------------------- +// Set the member in_domain and counter for all faces in the cdt +std::map + explore_subdomains(const CDT& cdt, + const CSGCGALDomain2D& total_domain, + const std::vector>& + subdomain_geometries) +{ + std::set not_visited; + + for(CDT::Finite_faces_iterator fit = cdt.finite_faces_begin(); + fit != cdt.finite_faces_end(); ++fit) + { + if (fit->is_in_domain()) + not_visited.insert(fit); + } + + std::map subdomain_map; + + std::list queue; + queue.push_back(*not_visited.begin()); + + while (!not_visited.empty()) + { + const Face_handle f = *not_visited.begin(); + + // set to default domain (0) + unsigned int marker = 0; + + const Point_2 p0 = f->vertex(0)->point(); + const Point_2 p1 = f->vertex(1)->point(); + const Point_2 p2 = f->vertex(2)->point(); + + const dolfin::Point p( (p0[0] + p1[0] + p2[0]) / 3.0, + (p0[1] + p1[1] + p2[1]) / 3.0 ); + + for (const std::pair subdomain : subdomain_geometries) + { + if (subdomain.second.point_in_domain(p)) + { + marker = subdomain.first; + break; + } + } + + explore_subdomain(cdt, + f, + marker, + subdomain_map, + not_visited); + } + + return subdomain_map; +} +//----------------------------------------------------------------------------- +double shortest_constrained_edge(const CDT &cdt) +{ + double min_length = std::numeric_limits::max(); + for (CDT::Finite_edges_iterator it = cdt.finite_edges_begin(); + it != cdt.finite_edges_end(); + it++) + { + if (!cdt.is_constrained(*it)) + continue; + + // An edge is an std::pair + // see CGAL/Triangulation_data_structure_2.h + CDT::Face_handle f = it->first; + const int i = it->second; + + CDT::Point p1 = f->vertex( (i+1)%3 )->point(); + CDT::Point p2 = f->vertex( (i+2)%3 )->point(); + + min_length = std::min(CGAL::to_double((p1-p2).squared_length()), + min_length); + } + + return min_length; +} +//----------------------------------------------------------------------------- +std::shared_ptr CSGCGALMeshGenerator2D::generate(const std::shared_ptr total_domain, + const std::vector>>& subdomains) +{ + const bool partition = parameters["partition"]; + + std::shared_ptr mesh(new dolfin::Mesh); + + // Note that if not in parallel (ie. size() == 0) + // then both receiver and broadcaster will return false + // If not partition, then generate the mesh on all processes + if (!partition || !dolfin::MPI::is_receiver(mesh->mpi_comm())) + { + double cell_size; + { + const double mesh_resolution = parameters["mesh_resolution"]; + if (mesh_resolution > 0) + { + const double min_radius = total_domain->compute_boundingcircle_radius(); + cell_size = 2.0*min_radius/mesh_resolution; + } + else + cell_size = parameters["cell_size"]; + } + + std::vector > + subdomain_geometries; + + log(dolfin::TRACE, "Converting geometry to CGAL polygon"); + + // Empty polygon, will be populated when traversing the subdomains + CSGCGALDomain2D overlaying; + + if (!subdomains.empty()) + log(dolfin::TRACE, "Processing subdomains"); + + // Add the subdomains to the PSLG. Traverse in reverse order to get the latest + // added subdomain on top + for (auto rit = subdomains.rbegin(); rit != subdomains.rend(); rit++) + { + const std::size_t current_index = rit->first; + CSGCGALDomain2D cgal_geometry(*(rit->second)); + + // Only the part inside the total domain + cgal_geometry.intersect_inplace(*total_domain, 1e-15); + + // Only the part outside overlaying subdomains + cgal_geometry.difference_inplace(overlaying); + + subdomain_geometries.push_back(std::make_pair(current_index, + cgal_geometry)); + + overlaying.join_inplace(cgal_geometry); + } + + CSGCGALDomain2D remaining(*total_domain); + remaining.difference_inplace(overlaying); + + subdomain_geometries.push_back(std::make_pair(0, remaining)); + + const auto pslg = CSGCGALDomain2D::compute_pslg(subdomain_geometries); + + + // for (dolfin::Point p : pslg.first) + // { + // std::cout << "\"Point " << p.x() << " " << p.y() << "\" "; + // } + // std::cout << std::endl; + + // for (std::pair s : pslg.second) + // { + // dolfin::Point p0 = pslg.first[s.first]; + // dolfin::Point p1 = pslg.first[s.second]; + // std::cout << "\"Segment " << p0.x() << " " << p0.y() << ", " << p1.x() << " " << p1.y() << "\" "; + // } + // std::cout << std::endl; + + // Create empty CGAL triangulation and copy data from the PSLG + CDT cdt; + + { + std::vector vertices; + { + // Insert the vertices into the triangulation + vertices.reserve(pslg.first.size()); + for (const dolfin::Point& vertex : pslg.first) + { + const Point_2 p(vertex.x(), vertex.y()); + vertices.push_back(cdt.insert(p)); + } + } + + // Insert the edges as constraints + for (const std::pair& edge : pslg.second) + { + cdt.insert_constraint(vertices[edge.first], vertices[edge.second]); + } + } + + log(dolfin::TRACE, "Initializing mesh refinement"); + + // Create mesher + CGAL_Mesher_2 mesher(cdt); + + // Add seeds for all faces in the total domain + std::vector hole_points; + total_domain->get_points_in_holes(hole_points); + std::vector seed_points; + for (dolfin::Point p : hole_points) + seed_points.push_back(Point_2(p.x(), p.y())); + + log(dolfin::TRACE, "Added %d seed points", seed_points.size()); + mesher.set_seeds(seed_points.begin(), seed_points.end(), false); + + // Set shape and size criteria + const double shape_bound = parameters["triangle_shape_bound"]; + mesher.set_criteria(Mesh_criteria_2(shape_bound, cell_size)); + + // Refine CGAL mesh/triangulation + mesher.refine_mesh(); + + // Lloyd optimization + if (parameters["lloyd_optimize"]) { + log(dolfin::TRACE, "Optimizing mesh by Lloyd smoothing"); + CGAL::lloyd_optimize_mesh_2(cdt); + } + + // Make sure triangulation is valid + dolfin_assert(cdt.is_valid()); + + // Mark the subdomains + log(dolfin::TRACE, "Exploring subdomains in mesh"); + + std::map subdomain_map = + explore_subdomains(cdt, *total_domain, subdomain_geometries); + + // Clear mesh + // This function has been removed from dolfin. + // mesh.clear(); + + const std::size_t num_vertices = cdt.number_of_vertices(); + + // Count valid cells + std::size_t num_cells = 0; + for (CDT::Finite_faces_iterator cgal_cell = cdt.finite_faces_begin(); + cgal_cell != cdt.finite_faces_end(); ++cgal_cell) + { + // Add cell if it is in the domain + if (cgal_cell->is_in_domain()) + { + num_cells++; + } + } + + log(dolfin::DBG, "Mesh with %d vertices and %d cells created", num_vertices, num_cells); + + // Create a MeshEditor and open + dolfin::MeshEditor mesh_editor; + mesh_editor.open(*mesh, dolfin::CellType::Type::triangle, 2, 2); + mesh_editor.init_vertices(num_vertices); + mesh_editor.init_cells(num_cells); + + // Add vertices to mesh + unsigned int vertex_index = 0; + std::map vertex_map; + for (CDT::Finite_vertices_iterator cgal_vertex = cdt.finite_vertices_begin(); + cgal_vertex != cdt.finite_vertices_end(); ++cgal_vertex) + { + // Get vertex coordinates and add vertex to the mesh + dolfin::Point p; + p[0] = cgal_vertex->point()[0]; + p[1] = cgal_vertex->point()[1]; + + // Add mesh vertex + mesh_editor.add_vertex(vertex_index, p); + + // Attach index to vertex and increment + vertex_map[cgal_vertex] = vertex_index; + vertex_index++; + } + + dolfin_assert(vertex_index == num_vertices); + + // Add cells to mesh and build domain marker mesh function + dolfin::MeshDomains &domain_markers = mesh->domains(); + std::size_t cell_index = 0; + const bool mark_cells = !subdomains.empty(); + for (CDT::Finite_faces_iterator cgal_cell = cdt.finite_faces_begin(); + cgal_cell != cdt.finite_faces_end(); ++cgal_cell) + { + // Add cell if it is in the domain + if (cgal_cell->is_in_domain()) + { + mesh_editor.add_cell(cell_index, + vertex_map[cgal_cell->vertex(0)], + vertex_map[cgal_cell->vertex(1)], + vertex_map[cgal_cell->vertex(2)]); + + if (mark_cells) + { + domain_markers.set_marker(std::make_pair(cell_index, + subdomain_map[cgal_cell]), 2); + } + ++cell_index; + } + } + dolfin_assert(cell_index == num_cells); + + // Close mesh editor + mesh_editor.close(); + } + + // Distribute the mesh (if in parallel) + if (partition) + dolfin::MeshPartitioning::build_distributed_mesh(*mesh); + + return mesh; +} +} +//----------------------------------------------------------------------------- diff --git a/src/CSGCGALMeshGenerator3D.cpp b/src/CSGCGALMeshGenerator3D.cpp new file mode 100644 index 0000000..d7c2cb2 --- /dev/null +++ b/src/CSGCGALMeshGenerator3D.cpp @@ -0,0 +1,382 @@ +// Copyright (C) 2012-2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define CGAL_NO_DEPRECATED_CODE +#define CGAL_MESH_3_NO_DEPRECATED_SURFACE_INDEX +#define CGAL_MESH_3_NO_DEPRECATED_C3T3_ITERATORS + +#include +#include +#include +#include +#include "Polyhedral_multicomponent_mesh_domain_with_features_3.h" +#include "make_multicomponent_mesh_3.h" + +// Bounding sphere computation +#include +#include + +//#define NO_MULTICOMPONENT_DOMAIN + +// Domain +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; +typedef CGAL::Mesh_polyhedron_3::type MeshPolyhedron_3; +typedef K::Point_3 Point_3; +typedef K::Vector_3 Vector_3; +typedef K::Triangle_3 Triangle_3; + +#ifdef NO_MULTICOMPONENT_DOMAIN +typedef CGAL::Polyhedral_mesh_domain_with_features_3 PolyhedralMeshDomain; +#else +typedef Polyhedral_multicomponent_mesh_domain_with_features_3 PolyhedralMeshDomain; +#endif + +// Triangulation +typedef CGAL::Mesh_triangulation_3::type Tr; +// typedef CGAL::Mesh_complex_3_in_triangulation_3< +// Tr,PolyhedralMeshDomain::Corner_index,PolyhedralMeshDomain::Curve_segment_index> C3t3; + +typedef CGAL::Mesh_complex_3_in_triangulation_3 C3t3; + +// Criteria +typedef CGAL::Mesh_criteria_3 Mesh_criteria; + +namespace +{ +//----------------------------------------------------------------------------- +// Repeatedly output some data from the triangulation. Return when the mutex is +// released. +void output_num_vertices(Tr& triangulation, std::timed_mutex& mutex) +{ + std::chrono::seconds timeout(1); + + while (!mutex.try_lock_for(timeout)) + { + // Be carefull to only call functions which are thread safe. + // Inspecting the triangulation data structure reveals that both + // Tr::number_of_vertices() and Tr::number_of_cells() just reads an + // int. Both Tr::number_of_finite_cells and + // Tr::number_of_finite_vertices() count by iterating through the data + // (which creates a race condition). + dolfin::log(dolfin::PROGRESS, + "Generating mesh: Currently approx. %ld vertices and %ld cells", + triangulation.number_of_vertices(), + triangulation.number_of_cells()); + } +} +//----------------------------------------------------------------------------- +void build_dolfin_mesh(const C3t3& c3t3, dolfin::Mesh& mesh) +{ + typedef C3t3::Triangulation Triangulation; + typedef Triangulation::Vertex_handle Vertex_handle; + + // Collect the vertices that are part of the complex + std::map vertex_id_map; + for(C3t3::Cells_in_complex_iterator cit = c3t3.cells_in_complex_begin(); + cit != c3t3.cells_in_complex_end(); + ++cit) + { + for (std::size_t i = 0; i < 4; i++) + { + if (!vertex_id_map.count(cit->vertex(i))) + { + const std::size_t vertex_index = vertex_id_map.size(); + vertex_id_map[cit->vertex(i)] = vertex_index; + } + } + } + + // Create and initialize mesh editor + + // dolfin::Mesh::clear has been removed + // mesh.clear(); + + dolfin::MeshEditor mesh_editor; + mesh_editor.open(mesh, dolfin::CellType::Type::tetrahedron, 3, 3); + mesh_editor.init_vertices(vertex_id_map.size()); + mesh_editor.init_cells(c3t3.number_of_cells_in_complex()); + + // Add vertices to mesh + for (std::map::const_iterator it = vertex_id_map.cbegin(); + it != vertex_id_map.cend(); it++) + { + // Get vertex coordinates and add vertex to the mesh + dolfin::Point p(it->first->point()[0], it->first->point()[1], it->first->point()[2]); + mesh_editor.add_vertex(it->second, p); + } + + // Add cells to mesh + std::size_t cell_index = 0; + for(C3t3::Cells_in_complex_iterator cit = c3t3.cells_in_complex_begin(); + cit != c3t3.cells_in_complex_end(); + ++cit) + { + mesh_editor.add_cell(cell_index, + vertex_id_map[cit->vertex(0)], + vertex_id_map[cit->vertex(1)], + vertex_id_map[cit->vertex(2)], + vertex_id_map[cit->vertex(3)]); + + ++cell_index; + } + + // Close mesh editor + mesh_editor.close(); +} +//----------------------------------------------------------------------------- +struct Copy_polyhedron_to + : public CGAL::Modifier_base +{ + Copy_polyhedron_to(const mshr::CSGCGALDomain3D& in_poly, bool flip) + : _in_poly(in_poly), flip(flip) {} + + void operator()(typename MeshPolyhedron_3::HalfedgeDS& out_hds) + { + typedef typename MeshPolyhedron_3::HalfedgeDS Output_HDS; + CGAL::Polyhedron_incremental_builder_3 builder(out_hds); + + builder.begin_surface(_in_poly.num_vertices(), + _in_poly.num_facets(), + _in_poly.num_halfedges()); + + { + std::unique_ptr> v = _in_poly.get_vertices(); + + for (std::size_t i = 0; i < v->size(); i += 3) + { + typename MeshPolyhedron_3::Point_3 p( (*v)[i], (*v)[i+1], (*v)[i+2] ); + builder.add_vertex(p); + } + } + + { + std::unique_ptr> f = _in_poly.get_facets(); + + for (std::size_t i = 0; i < f->size(); i += 3) + { + builder.begin_facet(); + builder.add_vertex_to_facet( (*f)[i] ); + if (flip) + { + builder.add_vertex_to_facet( (*f)[i+2] ); + builder.add_vertex_to_facet( (*f)[i+1] ); + } + else + { + builder.add_vertex_to_facet( (*f)[i+1] ); + builder.add_vertex_to_facet( (*f)[i+2] ); + } + + builder.end_facet(); + } + } + builder.end_surface(); + } +private: + const mshr::CSGCGALDomain3D& _in_poly; + const bool flip; +}; + +static void convert_to_inexact(const mshr::CSGCGALDomain3D &exact_domain, + MeshPolyhedron_3 &inexact_domain, + bool flip) +{ + Copy_polyhedron_to modifier(exact_domain, flip); + inexact_domain.delegate(modifier); + CGAL_assertion(inexact_domain.is_valid()); +} +//----------------------------------------------------------------------------- +double get_bounding_sphere_radius(const MeshPolyhedron_3& polyhedron) +{ + typedef CGAL::Min_sphere_of_spheres_d_traits_3 MinSphereTraits; + typedef CGAL::Min_sphere_of_spheres_d Min_sphere; + typedef MinSphereTraits::Sphere Sphere; + + std::vector S; + + for (MeshPolyhedron_3::Vertex_const_iterator it=polyhedron.vertices_begin(); + it != polyhedron.vertices_end(); ++it) + { + S.push_back(Sphere(it->point(), 0.0)); + } + + Min_sphere ms(S.begin(), S.end()); + CGAL_assertion(ms.is_valid()); + + return CGAL::to_double(ms.radius()); +} +} //end anonymous namespace +//----------------------------------------------------------------------------- +namespace mshr +{ +CSGCGALMeshGenerator3D::CSGCGALMeshGenerator3D() +{ + parameters = default_parameters(); +} +//----------------------------------------------------------------------------- +CSGCGALMeshGenerator3D::~CSGCGALMeshGenerator3D() {} +//----------------------------------------------------------------------------- +void CSGCGALMeshGenerator3D::generate(std::shared_ptr csgdomain, + dolfin::Mesh& mesh) const +{ + + // Note that if not in parallel (ie. size() == 0) + // then both receiver and broadcaster will return false + if (!dolfin::MPI::is_receiver(mesh.mpi_comm())) + { + // Create CGAL mesh domain + MeshPolyhedron_3 p; + convert_to_inexact(*csgdomain, p, !csgdomain->is_insideout()); + + // Reset the (memory consuming exact arithmetic) domain object + // will be deleted if the pointer has been moved to the function + csgdomain.reset(); + + // Workaround, cgal segfaulted when assigning new mesh criterias + // within the if-else blocks. + std::unique_ptr criteria; + double edge_size; + + const bool criteria_changed = parameters["edge_size"].change_count() > 0 + || parameters["facet_angle"].change_count() > 0 + || parameters["facet_size"].change_count() > 0 + || parameters["facet_distance"].change_count() > 0 + || parameters["cell_radius_edge_ratio"].change_count() > 0 + || parameters["cell_size"].change_count() > 0; + + if (parameters["mesh_resolution"].change_count() > 0 && criteria_changed) + dolfin::warning("Attempt to set both mesh_resolution and other meshing criterias which are mutually exclusive"); + + if (criteria_changed) + { + log(dolfin::TRACE, "Using user specified meshing criterias"); + + // Mesh criteria + criteria.reset(new Mesh_criteria(CGAL::parameters::edge_size = parameters["edge_size"], + CGAL::parameters::facet_angle = parameters["facet_angle"], + CGAL::parameters::facet_size = parameters["facet_size"], // <----------- + CGAL::parameters::facet_distance = parameters["facet_distance"], + CGAL::parameters::cell_radius_edge_ratio = parameters["cell_radius_edge_ratio"], + CGAL::parameters::cell_size = parameters["cell_size"])); // <-------------- + edge_size = parameters["edge_size"]; + } + else + { + // Try to compute reasonable parameters + const double mesh_resolution = parameters["mesh_resolution"]; + const double r = get_bounding_sphere_radius(p); + const double cell_size = r/mesh_resolution*2.0; + log(dolfin::TRACE, "Computing meshing criterias. Chose cell size %f", cell_size); + + criteria.reset(new Mesh_criteria(CGAL::parameters::edge_size = cell_size, + CGAL::parameters::facet_angle = 30.0, + CGAL::parameters::facet_size = cell_size, + CGAL::parameters::facet_distance = cell_size/10.0, // ??? + CGAL::parameters::cell_radius_edge_ratio = 3.0, + CGAL::parameters::cell_size = cell_size)); + edge_size = cell_size; + } + + #ifdef NO_MULTICOMPONENT_DOMAIN + PolyhedralMeshDomain domain(p); + #else + PolyhedralMeshDomain domain(p, edge_size); + #endif + + if (parameters["detect_sharp_features"]) + { + log(dolfin::TRACE, "Detecting sharp features"); + + const double feature_threshold = parameters["feature_threshold"]; + domain.detect_features(feature_threshold); + } + + // Mesh generation + log(dolfin::TRACE, "Generating mesh"); + C3t3 c3t3; + { + std::timed_mutex mutex; + mutex.lock(); + std::thread output_thread(output_num_vertices, + std::ref(c3t3.triangulation()), std::ref(mutex)); + dolfin::begin("Generating mesh with CGAL 3D mesh generator"); + make_multicomponent_mesh_3_impl(c3t3, + domain, + *criteria, + true); + mutex.unlock(); + output_thread.join(); + dolfin::end(); + } + + log(dolfin::TRACE, "Done"); + if (parameters["odt_optimize"]) + { + log(dolfin::TRACE, "Optimizing mesh by odt optimization"); + odt_optimize_mesh_3(c3t3, domain); + } + + if (parameters["lloyd_optimize"]) + { + log(dolfin::TRACE, "Optimizing mesh by lloyd optimization"); + lloyd_optimize_mesh_3(c3t3, domain); + } + + if (parameters["perturb_optimize"]) + { + log(dolfin::TRACE, "Optimizing mesh by perturbation"); + // TODO: Set time limit + CGAL::perturb_mesh_3(c3t3, domain); + } + + if (parameters["exude_optimize"]) + { + log(dolfin::TRACE, "Optimizing mesh by sliver exudation"); + exude_mesh_3(c3t3); + } + + build_dolfin_mesh(c3t3, mesh); + } + + // Distribute the mesh (if in parallel) + dolfin::MeshPartitioning::build_distributed_mesh(mesh); +} +//----------------------------------------------------------------------------- +std::shared_ptr +CSGCGALMeshGenerator3D::generate(std::shared_ptr csgdomain) const +{ + std::shared_ptr mesh(new dolfin::Mesh()); + generate(csgdomain, *mesh); + return mesh; +} +} diff --git a/src/CSGGeometries3D.cpp b/src/CSGGeometries3D.cpp new file mode 100644 index 0000000..43b4d99 --- /dev/null +++ b/src/CSGGeometries3D.cpp @@ -0,0 +1,262 @@ +// Copyright (C) 2012-2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// +// Modified by Johannes Ring 2014 +// Modified by Anders Logg 2014 + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "Polygon_utils.h" + +namespace mshr +{ + +// FIXME: Move somewhere else +// Convenience function for rotation +std::shared_ptr rotate(std::shared_ptr geometry, + dolfin::Point rot_axis, + dolfin::Point rot_center, + double theta) +{ + return std::shared_ptr(new CSGRotation(geometry, + rot_axis, + rot_center, + theta)); +} + +//----------------------------------------------------------------------------- +std::shared_ptr CSGGeometries::lego(std::size_t n0, + std::size_t n1, + std::size_t n2, + dolfin::Point x) +{ + // Standard dimensions for LEGO bricks / m + const double P = 8.0 * 0.001; + const double h = 3.2 * 0.001; + const double D = 5.0 * 0.001; + const double b = 1.7 * 0.001; + const double d = 0.2 * 0.001; + + // Create brick + std::shared_ptr + lego(new Box(x + dolfin::Point(0.5*d, 0.5*d, 0), + x + dolfin::Point(n0*P - 0.5*d, n1*P - 0.5*d, n2*h))); + + // Add knobs + for (std::size_t i = 0; i < n0; i++) + { + for (std::size_t j = 0; j < n1; j++) + { + const dolfin::Point knop_bottom = x + dolfin::Point( (i + 0.5)*P, + (j + 0.5)*P, + 0); + + std::shared_ptr + knob(new Cylinder(knop_bottom, + knop_bottom + dolfin::Point(0, 0, n2*h + b), + 0.5*D, + 0.5*D)); + + lego = lego + knob; + } + } + + return lego; +} +//----------------------------------------------------------------------------- +std::shared_ptr CSGGeometries::propeller(double r, + double R, + double w, + double h, + bool rotate_blades, + bool include_tip) +{ + // Parameters + const double v = 0.3; // rotation of blades + const double l = 0.8*w; // length of cone + + // // Create blades + std::shared_ptr + blade_0(new Box(dolfin::Point(0.8*r, -0.5*h, -0.5*w), + dolfin::Point( R, 0.5*h, 0.5*w))); + std::shared_ptr + blade_1(new Box(dolfin::Point( -R, -0.5*h, -0.5*w), + dolfin::Point(-0.8*r, 0.5*h, 0.5*w))); + std::shared_ptr + blade_2(new Box(dolfin::Point(-0.5*h, 0.8*r, -0.5*w), + dolfin::Point( 0.5*h, R, 0.5*w))); + std::shared_ptr + blade_3(new Box(dolfin::Point(-0.5*h, -R, -0.5*w), + dolfin::Point( 0.5*h, -0.8*r, 0.5*w))); + + // Rotate blades + if (rotate_blades) + { + blade_0 = rotate(blade_0, dolfin::Point(1, 0, 0), dolfin::Point(0, 0, 0), v); + blade_1 = rotate(blade_1, dolfin::Point(1, 0, 0), dolfin::Point(0, 0, 0), -v); + blade_2 = rotate(blade_2, dolfin::Point(0, 1, 0), dolfin::Point(0, 0, 0), v); + blade_3 = rotate(blade_3, dolfin::Point(0, 1, 0), dolfin::Point(0, 0, 0), -v); + } + + // Create blade tips + std::shared_ptr + blade_tip_0(new Cylinder(dolfin::Point( R, -0.5*h, 0), + dolfin::Point( R, 0.5*h, 0), 0.5*w, 0.5*w)); + std::shared_ptr + blade_tip_1(new Cylinder(dolfin::Point(-R, -0.5*h, 0), + dolfin::Point(-R, 0.5*h, 0), 0.5*w, 0.5*w)); + std::shared_ptr + blade_tip_2(new Cylinder(dolfin::Point(-0.5*h, R, 0), + dolfin::Point( 0.5*h, R, 0), 0.5*w, 0.5*w)); + std::shared_ptr + blade_tip_3(new Cylinder(dolfin::Point(-0.5*h, -R, 0), + dolfin::Point( 0.5*h, -R, 0), 0.5*w, 0.5*w)); + + // Rotate blades + if (rotate_blades) + { + blade_tip_0 = rotate(blade_tip_0, dolfin::Point(1, 0, 0), dolfin::Point(0, 0, 0), v); + blade_tip_1 = rotate(blade_tip_1, dolfin::Point(1, 0, 0), dolfin::Point(0, 0, 0), -v); + blade_tip_2 = rotate(blade_tip_2, dolfin::Point(0, 1, 0), dolfin::Point(0, 0, 0), v); + blade_tip_3 = rotate(blade_tip_3, dolfin::Point(0, 1, 0), dolfin::Point(0, 0, 0), -v); + } + + // Add blade tips + blade_0 = blade_0 + blade_tip_0; + blade_1 = blade_1 + blade_tip_1; + blade_2 = blade_2 + blade_tip_2; + blade_3 = blade_3 + blade_tip_3; + + // // Add blades + std::shared_ptr + blades = blade_0 + blade_1 + blade_2 + blade_3; + + // Create outer cylinder + std::shared_ptr + cylinder_outer(new Cylinder(dolfin::Point(0, 0, -0.5*w), + dolfin::Point(0, 0, 0.5*w), + r, r)); + + // Create inner cylinder + std::shared_ptr + cylinder_inner(new Cylinder(dolfin::Point(0, 0, -0.5*w), + dolfin::Point(0, 0, 0.5*w), + 0.5*r, 0.5*r)); + + // Create center cone + std::shared_ptr + cone(new Cylinder(dolfin::Point(0, 0, -0.5*w), + dolfin::Point(0, 0, -0.5*w - l), r, h)); + + // Create sphere for tip of cone + const double k = (r - h) / l; + const double rc = h*sqrt(1 + k*k); + const double xc = k*h; + std::shared_ptr + tip(new Sphere(dolfin::Point(0, 0, -0.5*w - l + xc), rc)); + + // Build propeller from parts + if (include_tip) + return cylinder_outer - cylinder_inner + blades + cone + tip; + else + return cylinder_outer - cylinder_inner + blades + cone; +} +//----------------------------------------------------------------------------- +std::shared_ptr CSGGeometries::import_mesh(std::shared_ptr mesh) +{ + std::shared_ptr g; + if (mesh->geometry().dim() == 2) + { + // Import and right hand orient boundary entities (not UFC orientation) + dolfin::BoundaryMesh bnd(*mesh, "exterior", false); + const std::vector& coordinates = bnd.coordinates(); + + std::vector> polygons; + std::vector> holes; + + std::map edges; + for (dolfin::EdgeIterator e(bnd); !e.end(); ++e) + { + const unsigned int* vertices = e->entities(0); + edges.emplace(std::make_pair(vertices[0], vertices[1])); + } + + while (edges.size() > 0) + { + std::vector polygon_vertices; + std::map::iterator current = edges.begin(); + std::size_t start = current->first; + std::size_t next; + do + { + polygon_vertices.push_back(dolfin::Point(coordinates[2*current->second], coordinates[2*current->second+1])); + next = current->second; + edges.erase(current); + current = edges.find(next); + } while(next != start); + + if (PolygonUtils::ccw(polygon_vertices)) + polygons.push_back(polygon_vertices); + else + { + std::reverse(polygon_vertices.begin(), polygon_vertices.end()); + holes.push_back(polygon_vertices); + } + } + + { + std::vector>::iterator it = polygons.begin(); + g = std::make_shared(*it); + it++; + + for(; it != polygons.end(); it++) + { + std::shared_ptr h = std::make_shared(*it); + g = std::make_shared(g, h); + } + } + + for (std::vector>::iterator it = holes.begin(); + it != holes.end(); + it++) + { + std::shared_ptr h = std::make_shared(*it); + g = std::make_shared(g, h); + } + } + else + { + dolfin::dolfin_error("CSGGeometries.cpp", + "importing 3D mesh", + "use mshr::Surface3D for now"); + } + + return g; +} + + +} diff --git a/src/CSGGeometry.cpp b/src/CSGGeometry.cpp new file mode 100644 index 0000000..17d522b --- /dev/null +++ b/src/CSGGeometry.cpp @@ -0,0 +1,137 @@ +// Copyright (C) 2012 Anders Logg, Benjamin Kehlet 2013-2017 +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#include +#include +#include + +// Bounding sphere computation +#include +#include +#include + +namespace mshr +{ + +//----------------------------------------------------------------------------- +CSGGeometry::CSGGeometry() +{ + // Do nothing +} +//----------------------------------------------------------------------------- +CSGGeometry::~CSGGeometry() +{ + // Do nothing +} +//----------------------------------------------------------------------------- +void CSGGeometry::set_subdomain(std::size_t i, std::shared_ptr s) +{ + if (dim() != 2) + dolfin::dolfin_error("CSGGeometry.cpp", + "setting subdomain", + "Subdomains are currently supported only in 2D"); + + if (s->dim() != dim()) + dolfin::dolfin_error("CSGGeometry.cpp", + "setting subdomain", + "Subdomain and domain must be of same dimension. Domain was dimension %d and subdomain was %d", dim(), s->dim()); + + if (i == 0) + { + dolfin::dolfin_error("CSGGeometry.cpp", + "Setting reserved CSG subdomain (0)", + " Subdomain 0 is reserved and cannot be set by user"); + } + + // Check if i already used + std::list > >::iterator it = subdomains.begin(); + while (it != subdomains.end()) + { + if (it->first == i) + { + dolfin::warning("Double declaration of CSG subdomain with index %u.", i); + + // Remove existing declaration + it = subdomains.erase(it); + } + else + ++it; + } + + subdomains.push_back(std::make_pair(i, s)); +} +//----------------------------------------------------------------------------- +void CSGGeometry::set_subdomain(std::size_t i, CSGGeometry& s) +{ + set_subdomain(i, reference_to_no_delete_pointer(s)); +} +//----------------------------------------------------------------------------- +bool CSGGeometry::has_subdomains() const +{ + return subdomains.size() > 0; +} +//----------------------------------------------------------------------------- +bool CSGGeometry::inside(dolfin::Point p1, dolfin::Point p2) const +{ + dolfin_not_implemented(); + return false; +} +//----------------------------------------------------------------------------- +std::pair CSGGeometry::estimate_bounding_sphere(std::size_t numSamples) const +{ + std::pair aabb = bounding_box(); + const dolfin::Point min = aabb.first; + const dolfin::Point max = aabb.second; + + //typedef CGAL::Exact_predicates_exact_constructions_kernel K; + typedef CGAL::Cartesian K; + typedef K::Point_2 Point_2; + typedef CGAL::Min_sphere_of_spheres_d_traits_2 MinSphereTraits; + typedef CGAL::Min_sphere_of_spheres_d Min_sphere; + typedef MinSphereTraits::Sphere Sphere; + + std::vector S; + S.reserve(numSamples); + const std::size_t N = static_cast(std::sqrt(static_cast(numSamples)+.5)); + const double dX = (max.x()-min.x())/N; + const double dY = (max.y()-min.y())/N; + // const double dTheta = 2*DOLFIN_PI/N; + for (std::size_t i = 0; i < N; i++) + { + for (std::size_t j = 0; j < N; j++) + { + const dolfin::Point p(min.x() + i*dX, min.y() + j*dY); + if (inside(p)) + S.push_back(Sphere(Point_2(p.x(), p.y()), 0.0)); + } + } + + Min_sphere ms(S.begin(), S.end()); + CGAL_assertion(ms.is_valid()); + + auto it = ms.center_cartesian_begin(); + const double center_x = CGAL::to_double(*it); + it++; + const double center_y = CGAL::to_double(*it); + const double radius = CGAL::to_double(ms.radius()); + + return std::make_pair(dolfin::Point(center_x, center_y), + radius); +} + +} diff --git a/src/CSGOperators.cpp b/src/CSGOperators.cpp new file mode 100644 index 0000000..a01e48e --- /dev/null +++ b/src/CSGOperators.cpp @@ -0,0 +1,444 @@ +// Copyright (C) 2012 Anders Logg, Benjamin Kehlet 2013-2017 +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// +// Modified by Johannes Ring, 2012 + +#include + +#include +#include +#include + +#include + +namespace mshr +{ + +//----------------------------------------------------------------------------- +// CSGUnion +//----------------------------------------------------------------------------- +CSGOperator::CSGOperator() +{} + +//----------------------------------------------------------------------------- +// CSGUnion +//----------------------------------------------------------------------------- +CSGUnion::CSGUnion(std::shared_ptr g0, + std::shared_ptr g1) + : _g0(g0), _g1(g1) +{ + assert(g0); + assert(g1); + + // Check dimensions + if (g0->dim() != g1->dim()) + { + dolfin::dolfin_error("CSGOperators.cpp", + "create union of CSG geometries", + "Dimensions of geometries don't match (%d vs %d)", + g0->dim(), g1->dim()); + } + + dim_ = g0->dim(); +} +//----------------------------------------------------------------------------- +std::string CSGUnion::str(bool verbose) const +{ + assert(_g0); + assert(_g1); + + std::stringstream s; + + if (verbose) + { + s << "\n" + << "{\n" + << dolfin::indent(_g0->str(true)) + << "\n" + << dolfin::indent(_g1->str(true)) + << "\n}"; + } + else + { + s << "(" << _g0->str(false) << " + " << _g1->str(false) << ")"; + } + + return s.str(); +} +//----------------------------------------------------------------------------- +std::pair CSGUnion::bounding_box() const +{ + std::pair a = _g0->bounding_box(); + std::pair b = _g1->bounding_box(); + + return std::make_pair(dolfin::Point(std::min(a.first.x(), b.first.x()), + std::min(a.first.y(), b.first.y()), + std::min(a.first.z(), b.first.z())), + dolfin::Point(std::max(a.second.x(), b.second.x()), + std::max(a.second.y(), b.second.y()), + std::max(a.second.z(), b.second.z()))); +} +//----------------------------------------------------------------------------- +bool CSGUnion::inside(dolfin::Point p) const +{ + return _g0->inside(p) || _g1->inside(p); +} + +//----------------------------------------------------------------------------- +// CSGDifference +//----------------------------------------------------------------------------- +CSGDifference::CSGDifference(std::shared_ptr g0, + std::shared_ptr g1) + : _g0(g0), _g1(g1) +{ + assert(g0); + assert(g1); + + // Check dimensions + if (g0->dim() != g1->dim()) + { + dolfin::dolfin_error("CSGOperators.cpp", + "create difference of CSG geometries", + "Dimensions of geomestries don't match (%d vs %d)", + g0->dim(), g1->dim()); + } + + dim_ = g0->dim(); +} +//----------------------------------------------------------------------------- +std::string CSGDifference::str(bool verbose) const +{ + assert(_g0); + assert(_g1); + + std::stringstream s; + + if (verbose) + { + s << "\n" + << "{\n" + << dolfin::indent(_g0->str(true)) + << "\n" + << dolfin::indent(_g1->str(true)) + << "\n}"; + } + else + { + s << "(" << _g0->str(false) << " - " << _g1->str(false) << ")"; + } + + return s.str(); +} +//----------------------------------------------------------------------------- +std::pair CSGDifference::bounding_box() const +{ + // FIXME: This is where the bounding_box implementation may overshoot + return _g0->bounding_box(); +} +//----------------------------------------------------------------------------- +bool CSGDifference::inside(dolfin::Point p) const +{ + return _g0->inside(p) && !_g1->inside(p); +} + +//----------------------------------------------------------------------------- +// CSGIntersection +//----------------------------------------------------------------------------- +CSGIntersection::CSGIntersection(std::shared_ptr g0, + std::shared_ptr g1) + : _g0(g0), _g1(g1) +{ + assert(g0); + assert(g1); + + // Check dimensions + if (g0->dim() != g1->dim()) + { + dolfin::dolfin_error("CSGOperators.cpp", + "create intersection of CSG geometries", + "Dimensions of geomestries don't match (%d vs %d)", + g0->dim(), g1->dim()); + } + + dim_ = g0->dim(); +} +//----------------------------------------------------------------------------- +std::string CSGIntersection::str(bool verbose) const +{ + assert(_g0); + assert(_g1); + + std::stringstream s; + + if (verbose) + { + s << "\n" + << "{\n" + << dolfin::indent(_g0->str(true)) + << "\n" + << dolfin::indent(_g1->str(true)) + << "\n}"; + } + else + { + s << "(" << _g0->str(false) << " * " << _g1->str(false) << ")"; + } + + return s.str(); +} +//----------------------------------------------------------------------------- +std::pair CSGIntersection::bounding_box() const +{ + std::pair a = _g0->bounding_box(); + std::pair b = _g1->bounding_box(); + + return std::make_pair(dolfin::Point(std::min(a.first.x(), b.first.x()), + std::min(a.first.y(), b.first.y()), + std::min(a.first.z(), b.first.z())), + dolfin::Point(std::max(a.second.x(), b.second.x()), + std::max(a.second.y(), b.second.y()), + std::max(a.second.z(), b.second.z()))); +} +//----------------------------------------------------------------------------- +bool CSGIntersection::inside(dolfin::Point p) const +{ + return _g0->inside(p) && _g1->inside(p); +} + +//----------------------------------------------------------------------------- +// CSGTranslation +//----------------------------------------------------------------------------- +CSGTranslation::CSGTranslation(std::shared_ptr g, + dolfin::Point t) + : g(g), t(t) +{ + assert(g); + + dim_ = g->dim(); +} +//----------------------------------------------------------------------------- +std::string CSGTranslation::str(bool verbose) const +{ + std::stringstream s; + + if (verbose) + { + s << "\n" + << "{\n" + << dolfin::indent(g->str(true) + "\nby\n" + t.str(true)) + << "\n}"; + } + else + { + s << "(" << g->str(false) << " + " << t.str(false) << ")"; + } + + return s.str(); +} +//----------------------------------------------------------------------------- +std::pair CSGTranslation::bounding_box() const +{ + std::pair aabb = g->bounding_box(); + + return std::make_pair(aabb.first+t, aabb.second+t); +} +//----------------------------------------------------------------------------- +bool CSGTranslation::inside(dolfin::Point p) const +{ + return g->inside(p-t); +} + +//----------------------------------------------------------------------------- +// CSGScaling +//----------------------------------------------------------------------------- +CSGScaling::CSGScaling(std::shared_ptr g, + dolfin::Point c, + double s) + : g(g), c(c), s(s), translate(true) +{ + assert(g); + + dim_ = g->dim(); +} +//----------------------------------------------------------------------------- +CSGScaling::CSGScaling(std::shared_ptr g, + double s) + : g(g), c(0,0,0), s(s), translate(false) +{ + assert(g); + + dim_ = g->dim(); +} +//----------------------------------------------------------------------------- +std::string CSGScaling::str(bool verbose) const +{ + std::stringstream ss; + + if (verbose) + { + ss << "\n" + << "{\n" + << dolfin::indent(g->str(true) + "\nby\n" + std::to_string(s)); + + if (translate) + ss << "\naround " << c.str(true); + + ss << "\n}"; + } + else + { + ss << "(" << g->str(false) << " * " << std::to_string(s); + if (translate) + ss << "(" << c.str(true) << ")"; + ss << ")"; + } + + return ss.str(); +} +//----------------------------------------------------------------------------- +std::pair CSGScaling::bounding_box() const +{ + std::pair aabb = g->bounding_box(); + + const dolfin::Point scaled_c = (translate ? (1-s)*c : dolfin::Point(0,0,0)); + + if (translate) + return std::make_pair((aabb.first-c)*s + c, (aabb.second-c)*s + c); + else + return std::make_pair(aabb.first*s, aabb.second*s); + +} +//----------------------------------------------------------------------------- +bool CSGScaling::inside(dolfin::Point p) const +{ + const dolfin::Point p_scaled = (translate ? (p-c)*s + c : p*s); + return g->inside(p_scaled); +} + +//----------------------------------------------------------------------------- +// CSGRotation +//----------------------------------------------------------------------------- +CSGRotation::CSGRotation(std::shared_ptr g, + double theta) + : g(g), rot_axis(.0,.0), c(.0,.0), theta(theta), translate(false) +{ + + dim_ = g->dim(); + + if (dim_ > 2) + dolfin::dolfin_error("CSGOperators.cpp", + "Constructing CSG rotation", + "Rotation axis must be given in 3D"); +} +//----------------------------------------------------------------------------- +CSGRotation::CSGRotation(std::shared_ptr g, + dolfin::Point v, + double theta) + : g(g), + rot_axis(v), + c(v), + theta(theta), + translate(g->dim() == 2 ? true : false) +{ + assert(g); + + dim_ = g->dim(); + + // if (dim_ == 2) + // translate = true; + // else + // translate = false; +} +//----------------------------------------------------------------------------- +CSGRotation::CSGRotation(std::shared_ptr g, + dolfin::Point rot_axis, + dolfin::Point rot_center, + double theta) + : g(g), + rot_axis(rot_axis), + c(rot_center), + theta(theta), + translate(true) +{ + assert(g); + + dim_ = g->dim(); + + if (dim_ < 3) + dolfin::dolfin_error("CSGOperators.cpp", + "Constructing CSG rotation", + "Can't give rotation axis for dimension < 3"); +} +//----------------------------------------------------------------------------- +std::string CSGRotation::str(bool verbose) const +{ + std::stringstream ss; + + if (verbose) + { + ss << "\n" + << "{\n" + << dolfin::indent(g->str(true) + + (translate ? "\naround "+rot_axis.str(true) : "") + + "\nby " + std::to_string(theta/DOLFIN_PI) + " PI"); + + ss << "\n}"; + } + else + { + ss << "rotate(" << g->str(false) + << ", " << std::to_string(theta/DOLFIN_PI) << " PI"; + + if (translate) + ss << ", " << rot_axis.str(true); + + ss << ")"; + } + + return ss.str(); +} +//----------------------------------------------------------------------------- +std::pair CSGRotation::bounding_box() const +{ + if (dim() != 2) + { + dolfin_not_implemented(); + } + + const std::pair aabb = g->bounding_box(); + const dolfin::Point axis(0,0,1); + + // Rotate all corners + const dolfin::Point a = aabb.first.rotate(axis, theta); + const dolfin::Point b = dolfin::Point(aabb.first.x(), aabb.second.y()).rotate(axis, theta); + const dolfin::Point c = dolfin::Point(aabb.second.x(), aabb.first.y()).rotate(axis, theta); + const dolfin::Point d = aabb.second.rotate(axis, theta); + + return std::make_pair(dolfin::Point(std::min(a.x(), std::min(b.x(), std::min(c.x(), d.x()))), + std::min(a.y(), std::min(b.y(), std::min(c.y(), d.y())))), + dolfin::Point(std::max(a.x(), std::max(b.x(), std::max(c.x(), d.x()))), + std::max(a.y(), std::max(b.y(), std::max(c.y(), d.y()))))); + +} +//----------------------------------------------------------------------------- +bool CSGRotation::inside(dolfin::Point p) const +{ + const dolfin::Point p_rotated = translate ? (p-c).rotate(dolfin::Point(0,0,1), -theta) + c : p.rotate(dolfin::Point(0,0,1), -theta); + return g->inside(p_rotated); +} + +} diff --git a/src/CSGPrimitives2D.cpp b/src/CSGPrimitives2D.cpp new file mode 100644 index 0000000..c91829e --- /dev/null +++ b/src/CSGPrimitives2D.cpp @@ -0,0 +1,285 @@ +// Copyright (C) 2012 Anders Logg, 2014-2017 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// +// Modified by Johannes Ring, 2012 + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace mshr +{ +//----------------------------------------------------------------------------- +CSGPrimitive2D::CSGPrimitive2D() +{} +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Circle +//----------------------------------------------------------------------------- +Circle::Circle(dolfin::Point(c), double r, std::size_t segments) + : c(c), _r(r), _segments(segments) +{ + if (_r < DOLFIN_EPS) + { + std::stringstream s; + s << "Circle with center " << c.str() << " has zero or negative radius"; + dolfin::dolfin_error("CSGPrimitives2D.cpp", + "create circle", + s.str()); + } + + if (0 < _segments && _segments < 3) + { + dolfin::dolfin_error("CSGPrimitives2D.cpp", + "create circle", + "Unable to create circle with fewer than 3 segments"); + } +} +//----------------------------------------------------------------------------- +std::string Circle::str(bool verbose) const +{ + std::stringstream s; + + if (verbose) + { + s << ""; + } + else + { + s << "Circle(" << c.str() << ", " << _r << ")"; + } + + return s.str(); +} +//----------------------------------------------------------------------------- +std::pair Circle::bounding_box() const +{ + return std::make_pair(dolfin::Point(c.x()-radius(), c.y()-radius()), + dolfin::Point(c.x()+radius(), c.y()+radius())); +} +//----------------------------------------------------------------------------- +bool Circle::inside(dolfin::Point p) const +{ + // dolfin::cout << "Inside: " << c << " r=" << _r << " ::: " << p << "(" << ((p-c).squared_norm() <= _r*_r ? "Inside" : "Outside") << dolfin::endl; + return (p-c).squared_norm() <= _r*_r; +} + + +//----------------------------------------------------------------------------- +// Ellipse +//----------------------------------------------------------------------------- +Ellipse::Ellipse(dolfin::Point c, double a, double b, + std::size_t segments) + : c(c), _a(a), _b(b), _segments(segments) +{ + if (_a < DOLFIN_EPS || _b < DOLFIN_EPS) + { + std::stringstream s; + s << "Ellipse with center " << c.str() << " has invalid semi-axis"; + dolfin::dolfin_error("CSGPrimitives2D.cpp", + "create ellipse", + s.str()); + } + + if (0 < _segments && _segments < 3) + { + dolfin::dolfin_error("CSGPrimitives2D.cpp", + "create ellipse", + "Unable to create ellipse with fewer than 3 segments"); + } +} +//----------------------------------------------------------------------------- +std::string Ellipse::str(bool verbose) const +{ + std::stringstream s; + + if (verbose) + { + s << ""; + } + else + { + s << "Ellipse(" << c.str() << ", " << _a << ", " << _b << ")"; + } + + return s.str(); +} +//----------------------------------------------------------------------------- +std::pair Ellipse::bounding_box() const +{ + return std::make_pair(dolfin::Point(c.x()-a(), c.y()-b()), + dolfin::Point(c.x()+a(), c.y()+b())); +} +//----------------------------------------------------------------------------- +bool Ellipse::inside(dolfin::Point p) const +{ + const double x = (c.x()-p.x())/_a; + const double y = (c.y()-p.y())/_b; + return x*x + y*y <= 1; +} + +//----------------------------------------------------------------------------- +// Rectangle +//----------------------------------------------------------------------------- +Rectangle::Rectangle(dolfin::Point a_, dolfin::Point b_) + : a(dolfin::Point(std::min(a_.x(), b_.x()), std::min(a_.y(), b_.y()))), + b(dolfin::Point(std::max(a_.x(), b_.x()), std::max(a_.y(), b_.y()))) +{ + if (dolfin::near(a.x(), b.x()) || dolfin::near( a.y(), b.y())) + { + std::stringstream s; + s << "Rectangle with corner " << a.str() << " and " << b.str() << " degenerated"; + dolfin::dolfin_error("CSGPrimitives2D.cpp", + "create rectangle", + s.str()); + } +} +//----------------------------------------------------------------------------- +std::string Rectangle::str(bool verbose) const +{ + std::stringstream s; + + if (verbose) + { + s << ""; + } + else + { + s << "Rectangle( (" << a.str() << "), (" << b.str() << ") )"; + } + + return s.str(); +} +//----------------------------------------------------------------------------- +std::pair Rectangle::bounding_box() const +{ + return std::make_pair(a, b); +} +//----------------------------------------------------------------------------- +bool Rectangle::inside(dolfin::Point p) const +{ + return p.x() >= a.x() && p.x() <= b.x() && p.y() >= a.y() && p.y() <= b.y(); +} + + +//----------------------------------------------------------------------------- +// Polygon +//----------------------------------------------------------------------------- +Polygon::Polygon(const std::vector& vertices) + : _vertices(vertices.begin(), vertices.end()) +{ + if (_vertices.size() < 3) + { + dolfin::dolfin_error("CSGPrimitives2D.cpp", + "create polygon", + "Polygon should have at least three vertices"); + } + + if (!ccw()) + dolfin::dolfin_error("CSGPrimitives2D.cpp", + "create polygon", + "Polygon vertices must be given in counter clockwise order"); +} +//----------------------------------------------------------------------------- +std::string Polygon::str(bool verbose) const +{ + std::stringstream s; + + if (verbose) + { + s << "::const_iterator p; + for (p = _vertices.begin(); p != _vertices.end(); ++p) + { + s << "(" << p->x() << ", " << p->y() << ")"; + if ((p != _vertices.end()) && (p + 1 != _vertices.end())) + s << ", "; + } + s << ">"; + } + else + { + s << "Polygon (" << _vertices.size() << " vertices)"; + } + + return s.str(); +} +//----------------------------------------------------------------------------- +bool Polygon::ccw() const +{ + double signed_area = 0.0; + + dolfin::Point prev = _vertices.back(); + for (std::vector::const_iterator it = _vertices.begin(), + v_end = _vertices.end(); + it != v_end; + ++it) + { + signed_area += (prev.x()*it->y())-(it->x()*prev.y()); + prev = *it; + } + + return signed_area > 0; +} +//----------------------------------------------------------------------------- +std::pair Polygon::bounding_box() const +{ + std::vector::const_iterator it = _vertices.begin(); + dolfin::Point min = *it; + dolfin::Point max = *it; + it++; + + for (; it != _vertices.end(); it++) + { + const dolfin::Point& p = *it; + min[0] = std::min(min[0], p[0]); + min[1] = std::min(min[1], p[1]); + min[2] = std::min(min[2], p[2]); + + max[0] = std::max(max[0], p[0]); + max[1] = std::max(max[1], p[1]); + max[2] = std::max(max[2], p[2]); + } + + return std::make_pair(min, max); +} +//----------------------------------------------------------------------------- +bool Polygon::inside(dolfin::Point p) const +{ + typedef CGAL::Cartesian K; + typedef CGAL::Point_2 Point_2; + typedef CGAL::Polygon_2 Polygon_2; + + Polygon_2 polygon; + for (const dolfin::Point& vertex : _vertices) + polygon.push_back(Point_2(vertex.x(), vertex.y())); + + return polygon.has_on_bounded_side(Point_2(p.x(), p.y())); +} + +} diff --git a/src/CSGPrimitives3D.cpp b/src/CSGPrimitives3D.cpp new file mode 100644 index 0000000..aa68614 --- /dev/null +++ b/src/CSGPrimitives3D.cpp @@ -0,0 +1,289 @@ +// Copyright (C) 2012 Anders Logg and 2012, 2014-2017 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#include + +#include +#include +#include + +namespace mshr +{ +//----------------------------------------------------------------------------- +CSGPrimitive3D::CSGPrimitive3D() +{} +//----------------------------------------------------------------------------- +std::pair CSGPrimitive3D::bounding_box() const +{ + dolfin_not_implemented(); + return std::make_pair(dolfin::Point(0., 0., 0.), dolfin::Point(0., 0., 0.)); +} +//----------------------------------------------------------------------------- +bool CSGPrimitive3D::inside(dolfin::Point p) const +{ + dolfin_not_implemented(); + return false; +} + + + +//----------------------------------------------------------------------------- +// Sphere +//----------------------------------------------------------------------------- +Sphere::Sphere(dolfin::Point center, double radius, std::size_t segments) + : c(center), r(radius), _segments(segments) +{ + if (r < DOLFIN_EPS) + { + dolfin::dolfin_error("CSGPrimitives3D.cpp", + "Create sphere", + "Sphere with center (%f, %f, %f) has zero or negative radius", c.x(), c.y(), c.z()); + } + + if (segments < 1) + { + dolfin::dolfin_error("CSGPrimitives3D.cpp", + "Create sphere", + "Can't create sphere with zero segments"); + } +} +//----------------------------------------------------------------------------- +std::string Sphere::str(bool verbose) const +{ + std::stringstream s; + + if (verbose) + { + s << ""; + } + else + s << "Sphere(" << c << ", " << r << ")"; + + return s.str(); +} +//----------------------------------------------------------------------------- +// Box +//----------------------------------------------------------------------------- +Box::Box(dolfin::Point a_, dolfin::Point b_) + : a(dolfin::Point(std::min(a_.x(), b_.x()), std::min(a_.y(), b_.y()), std::min(a_.z(), b_.z()))), + b(dolfin::Point(std::max(a_.x(), b_.x()), std::max(a_.y(), b_.y()), std::max(a_.z(), b_.z()))) +{ + if (dolfin::near(a.x(), b.x()) || dolfin::near(a.y(), b.y()) || dolfin::near(a.z(), b.z())) + { + std::stringstream s; + s << "Box with corner " << a_.str() << " and " << b_.str() << "is degenerate"; + + dolfin::dolfin_error("CSGPrimitives3D.cpp", + "Create axis aligned box", + s.str()); + } +} +//----------------------------------------------------------------------------- +std::string Box::str(bool verbose) const +{ + std::stringstream s; + + if (verbose) + { + s << ""; + } + else + { + s << "Box(" << a.str(false) << ", " << b.str(false) << ")"; + } + + return s.str(); +} +//----------------------------------------------------------------------------- +// Cone +//----------------------------------------------------------------------------- +Cylinder::Cylinder(dolfin::Point top, + dolfin::Point bottom, + double top_radius, + double bottom_radius, + std::size_t segments) + : _top(top), _bottom(bottom), _top_radius(top_radius), + _bottom_radius(bottom_radius), _segments(segments) +{ + if (dolfin::near(top_radius, 0.0) && dolfin::near(bottom_radius, 0.0)) + { + dolfin::dolfin_error("CSGPrimitives3D.cpp", + "Create cylinder", + "Cylinder with zero thickness"); + } + + if (top.distance(bottom) < DOLFIN_EPS) + { + dolfin::dolfin_error("CSGPrimitives3D.cpp", + "Create cylinder", + "Cylinder with zero length"); + } +} +//----------------------------------------------------------------------------- +std::string Cylinder::str(bool verbose) const +{ + std::stringstream s; + if (verbose) + { + s << ""; + } + else + { + s << "Cylinder( " << _top << ", " << _bottom << ", " << _top_radius + << ", " << _bottom_radius << " )"; + } + + return s.str(); +} +//----------------------------------------------------------------------------- +Tetrahedron::Tetrahedron(dolfin::Point a, + dolfin::Point b, + dolfin::Point c, + dolfin::Point d) + : a(a), b(b), c(c), d(d) +{ + // TODO: Check validity of coordinates +} +//----------------------------------------------------------------------------- +/// Informal string representation +std::string Tetrahedron::str(bool verbose) const +{ + std::stringstream s; + if (verbose) + { + s << ""; + } + else + { + s << "Tetrahedron( " << a << ", " << b << ", " << c << ", " + << d << ")"; + } + return s.str(); +} +//----------------------------------------------------------------------------- +Surface3D::Surface3D(std::string filename) + : _filename(filename), + mesh(NULL), + vertex_tolerance(.0), + degenerate_tolerance(1e-12), + repair(false), + single_connected_component(false), + sharp_features_filter(-1), + first_facet(0), + flip_facets(false), + debug_dump("") +{ + // Do nothing +} +//----------------------------------------------------------------------------- +Surface3D::Surface3D(std::shared_ptr m) + : _filename(""), + mesh(m), + vertex_tolerance(.0), + degenerate_tolerance(1e-12), + repair(false), + single_connected_component(false), + sharp_features_filter(-1), + first_facet(0), + cell_domain(0), + use_cell_domain(false) +{} +//----------------------------------------------------------------------------- +Surface3D::Surface3D(std::shared_ptr m, + std::size_t cell_domain) + : _filename(""), + mesh(m), + vertex_tolerance(.0), + degenerate_tolerance(1e-12), + repair(false), + single_connected_component(false), + sharp_features_filter(-1), + first_facet(0), + cell_domain(cell_domain), + use_cell_domain(true) +{} +//----------------------------------------------------------------------------- +std::string Surface3D::str(bool verbose) const +{ + return std::string("Surface3D from file ") + _filename; +} +//----------------------------------------------------------------------------- +Ellipsoid::Ellipsoid(dolfin::Point center, double a, double b, double c, std::size_t segments) + : center(center), a(a), b(b), c(c), _segments(segments) +{ + if (a < DOLFIN_EPS || b < DOLFIN_EPS || c < DOLFIN_EPS) + { + dolfin::dolfin_error("CSGPrimitives3D.cpp", + "Create ellipsoid", + "Ellipsoid with zero or negative semi-principal axis"); + } + + if (segments < 1) + { + dolfin::dolfin_error("CSGPrimitives3D.cpp", + "Create ellipsoid", + "Can't create ellipsoid with zero segments"); + } +} +//----------------------------------------------------------------------------- +std::string Ellipsoid::str(bool verbose) const +{ + std::stringstream ss; + if (verbose) + { + ss << "Ellipsoid centered at " << center.str() << " with semi-principal axes of lengths "; + ss << a << ", " << b << " and " << c; + } + else + { + ss << "Ellipsoid(" << center.str() << ", " << a << ", " << b << ", " << c << ")"; + } + return ss.str(); +} +//----------------------------------------------------------------------------- +Extrude2D::Extrude2D(std::shared_ptr geometry_2d, double z) + : geometry_2d(geometry_2d), z(z) +{ + if (geometry_2d->dim() != 2) + { + std::stringstream ss; + ss << "Expected geometry of dimension 2, got "; + ss << geometry_2d->dim(); + dolfin::dolfin_error("CSGPrimitives3D.cpp", + "Extrude 2D geometry", + ss.str()); +} +} +//----------------------------------------------------------------------------- +std::string Extrude2D::str(bool verbose) const +{ + std::stringstream ss; + ss << "Extruded 2D polygon, z = " << z; + if (verbose) + { + ss << geometry_2d->str(true); + } + + return ss.str(); +} +} diff --git a/src/DolfinMeshUtils.cpp b/src/DolfinMeshUtils.cpp new file mode 100644 index 0000000..1519a5c --- /dev/null +++ b/src/DolfinMeshUtils.cpp @@ -0,0 +1,298 @@ +// Copyright (C) 2014-2016 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#include +#include "FuzzyPointLocator.h" + +#include +#include +#include +#include +#include + +#include + +namespace mshr +{ + +std::pair DolfinMeshUtils::cell_volume_min_max(const dolfin::Mesh& m) +{ + std::pair res(std::numeric_limits::max(), 0.0); + + for (dolfin::CellIterator cell(m); !cell.end(); ++cell) + { + const double v = cell->volume(); + res.first = std::min(res.first, v); + res.second = std::max(res.second, v); + } + + return res; +} +//----------------------------------------------------------------------------- +bool DolfinMeshUtils::has_isolated_vertices(const dolfin::Mesh& m) +{ + std::set vertices; + for (dolfin::CellIterator cit(m); !cit.end(); ++cit) + { + const unsigned int* v = cit->entities(0); + for (std::size_t i = 0; i < cit->num_global_entities(0); i++) + { + vertices.insert(v[i]); + } + } + + bool isolated_vertices = false; + for (std::size_t i = 0; i < m.num_vertices(); i++) + { + if (vertices.count(i) < 1) + { + log(dolfin::DBG, "Vertex %u has no incident cells", i); + isolated_vertices = true; + } + } + + return isolated_vertices; +} +//----------------------------------------------------------------------------- +bool DolfinMeshUtils::check_mesh(const dolfin::Mesh& m) +{ + return !has_isolated_vertices(m); +} +//----------------------------------------------------------------------------- +std::shared_ptr + DolfinMeshUtils::extract_subdomain(std::shared_ptr mesh, + std::size_t cell_domain) +{ + dolfin_assert(mesh->geometry().dim() == 3); + dolfin_assert(mesh->topology().dim() == 3); + + // Collect all vertices incident to all marked cells + std::map collected_vertices; + std::size_t num_cells = 0; + for (const std::pair& marker : mesh->domains().markers(3)) + { + if (marker.second == cell_domain) + { + num_cells++; + dolfin::Cell c(*mesh, marker.second); + for (std::size_t i = 0; i < 4; i++) + { + const std::size_t s = collected_vertices.size(); + collected_vertices.insert(std::make_pair(c.entities(0)[i], s)); + } + } + } + + std::shared_ptr outmesh(new dolfin::Mesh); + dolfin::MeshEditor editor; + editor.open(*outmesh, dolfin::CellType::Type::tetrahedron, 3,3); + + editor.init_vertices(collected_vertices.size()); + for (std::pair v : collected_vertices) + { + dolfin::Vertex existing_vertex(*mesh, v.first); + editor.add_vertex(v.second, existing_vertex.point()); + } + + editor.init_cells(num_cells); + std::size_t cell_counter = 0; + for (const std::pair& marker : mesh->domains().markers(3)) + { + if (marker.second == cell_domain) + { + const dolfin::Cell c(*mesh, marker.second); + const unsigned int* vertices = c.entities(0); + editor.add_cell(cell_counter, + collected_vertices[vertices[0]], + collected_vertices[vertices[1]], + collected_vertices[vertices[2]], + collected_vertices[vertices[3]]); + + } + } + + editor.close(); + return outmesh; +} + + +//----------------------------------------------------------------------------- +std::shared_ptr +DolfinMeshUtils::merge_meshes(std::shared_ptr m1, + std::shared_ptr m2, + int m1_marker, + int m2_marker, + int m1_boundary_marker, + int m2_boundary_marker, + int interface_marker) +{ + log(dolfin::TRACE, "Merge meshes"); + + if (m1->topology().dim() != m2->topology().dim() || m1->geometry().dim() != m2->geometry().dim()) + dolfin::dolfin_error("DolfinMeshUtils.cpp", + "merging meshes", + "Meshes dimensions must match"); + + const std::size_t tdim = m1->topology().dim(); + + // Map vertex (geometric) points to index for m1 + FuzzyPointMap vertex_map_inner(1e-14); + std::vector map_to_mesh_index(m1->num_vertices());; + + for (dolfin::VertexIterator v(*m1); !v.end(); ++v) + { + dolfin_assert(!vertex_map_inner.contains(v->point())); + const std::size_t map_index = vertex_map_inner.insert_point(v->point()); + map_to_mesh_index[map_index] = v->global_index(); + } + + dolfin_assert(m1->num_vertices() == vertex_map_inner.size()); + + // Count number of shared vertices + // (We need this to initialize the mesh editor) + // TOOO: Store these to avoid another lookup? + std::size_t shared_vertices = 0; + for (dolfin::VertexIterator v(*m2); !v.end(); ++v) + { + if (vertex_map_inner.contains(v->point())) + shared_vertices++; + } + + log(dolfin::TRACE, "Located %u shared vertices.", shared_vertices); + + std::shared_ptr outmesh(new dolfin::Mesh); + dolfin::MeshEditor editor; + editor.open(*outmesh, m1->type().cell_type(), tdim, tdim); + editor.init_vertices(m1->num_vertices() + m2->num_vertices() - shared_vertices); + + // Add vertices from m1 + for (dolfin::VertexIterator v(*m1); !v.end(); ++v) + { + editor.add_vertex(v->global_index(), v->point()); + } + + // Add vertices from m2 + std::vector m2_vertex_map(m2->num_vertices()); + + std::size_t m2_vertex_counter = m1->num_vertices(); + for (dolfin::VertexIterator v(*m2); !v.end(); ++v) + { + std::size_t index; + if (vertex_map_inner.contains(v->point())) + { + const std::size_t map_index = vertex_map_inner.get_index(v->point()); + index = map_to_mesh_index[map_index]; + } + else + { + index = m2_vertex_counter; + editor.add_vertex(index, v->point()); + + m2_vertex_counter++; + } + + m2_vertex_map[v->global_index()] = index; + } + + editor.init_cells(m1->num_cells() + m2->num_cells()); + + std::size_t cell_counter = 0; + + std::vector vertices(tdim+1); + + // Add cells from m1 + for (dolfin::CellIterator c(*m1); !c.end(); ++c) + { + for (std::size_t i = 0; i < tdim+1; i++) + vertices[i] = c->entities(0)[i]; + + editor.add_cell(cell_counter,vertices); + cell_counter++; + } + + for (dolfin::CellIterator c(*m2); !c.end(); ++c) + { + for (std::size_t i = 0; i < tdim+1; i++) + vertices[i] = m2_vertex_map[c->entities(0)[i]]; + + editor.add_cell(cell_counter,vertices); + cell_counter++; + } + editor.close(); + + return outmesh; + + // Mark cells from + dolfin::MeshDomains &domain_markers = outmesh->domains(); + domain_markers.init(tdim-1); + domain_markers.init(tdim); + + for (std::size_t i = 0; i < outmesh->num_cells(); ++i) + { + if (m1->num_cells()) + { + if (m1_marker >= 0) + domain_markers.set_marker(std::make_pair(i, m1_marker), tdim); + } + else + { + if (m2_marker >= 0) + domain_markers.set_marker(std::make_pair(i, m2_marker), tdim); + } + } + + // Mark facets + // Boundary facets are marked with 1 or 2 depending on the source meshes the + // origined from. Facets shared by the two source meshes will be marked with + // 3. + + // Compute facet - cell connectivity + + outmesh->init(tdim-1, tdim); + for (dolfin::FacetIterator f(*outmesh); !f.end(); ++f) + { + if (f->exterior()) + { + if (f->entities(tdim)[0] < m1->num_cells()) + { + if (m1_boundary_marker >= 0) + domain_markers.set_marker(std::make_pair(f->index(), m1_boundary_marker), tdim-1); + } + else + { + if (m2_boundary_marker >= 0) + domain_markers.set_marker(std::make_pair(f->index(), m2_boundary_marker), tdim-1); + } + } + else + { + if (interface_marker >= 0) + { + const std::size_t cell1 = f->entities(tdim)[0]; + const std::size_t cell2 = f->entities(tdim)[1]; + if ( (cell1 < m1->num_cells() && cell2 >= m1->num_cells()) || + (cell1 >= m1->num_cells() && cell2 < m1->num_cells())) + domain_markers.set_marker(std::make_pair(f->index(), interface_marker), tdim-1); + } + } + } + + return outmesh; +} + +} diff --git a/src/FuzzyPointLocator.h b/src/FuzzyPointLocator.h new file mode 100644 index 0000000..12e2991 --- /dev/null +++ b/src/FuzzyPointLocator.h @@ -0,0 +1,145 @@ +// Copyright (C) 2016 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// +// Modified by Anders Logg 2016 + +#ifndef FUZZY_POINT_SET_H +#define FUZZY_POINT_SET_H + +#include // defines less operator for std::pair +#include +#include +#include +#include +#include +#include + +// TODO: Template over dimension. +//template +class FuzzyPointMap +{ + public: + FuzzyPointMap(double tolerance) + : tolerance(tolerance) + {} + //----------------------------------------------------------------------------- + template + std::size_t insert_point(const Point& p) + //----------------------------------------------------------------------------- + { + const std::size_t index = get_index(p); + if (index == points.size()) + { + // insert the points + points.push_back(std::array{{p[0], p[1], p[2]}}); + maps[0].insert(std::make_pair(p[0], index)); + maps[1].insert(std::make_pair(p[1], index)); + maps[2].insert(std::make_pair(p[2], index)); + } + + return index; + } + //----------------------------------------------------------------------------- + template + std::size_t forced_insert_point(const Point& p) + //----------------------------------------------------------------------------- + { + const std::size_t index = points.size(); + + points.push_back(std::array{{p[0], p[1], p[2]}}); + maps[0].insert(std::make_pair(p[0], index)); + maps[1].insert(std::make_pair(p[1], index)); + maps[2].insert(std::make_pair(p[2], index)); + + return index; + } + //----------------------------------------------------------------------------- + template + bool contains(const Point& p) + //----------------------------------------------------------------------------- + { + const std::size_t index = get_index(p); + return index < points.size(); + } + //----------------------------------------------------------------------------- + const std::array& operator[](std::size_t i) const + //----------------------------------------------------------------------------- + { + return points[i]; + } + //----------------------------------------------------------------------------- + const std::vector>& get_points() const + //----------------------------------------------------------------------------- + { + return points; + } + //----------------------------------------------------------------------------- + std::size_t size() const + //----------------------------------------------------------------------------- + { + return points.size(); + } + //----------------------------------------------------------------------------- + template + std::size_t get_index(const Point& p) + //----------------------------------------------------------------------------- + { + typedef std::multimap::iterator iterator; + + // Check if a nearby point exists + std::array, 3> matches; + for (int i = 0; i < 3; i++) + { + iterator lb = maps[i].lower_bound(p[i]-tolerance); + iterator ub = maps[i].upper_bound(p[i]+tolerance); + + if (lb != maps[i].end()) + { + iterator it = lb; + while (it != ub) + { + matches[i].insert(it->second); + it++; + } + } + } + + std::set x_y_intersections; + std::set_intersection(matches[0].begin(), matches[0].end(), + matches[1].begin(), matches[1].end(), + std::inserter(x_y_intersections, x_y_intersections.begin())); + + std::set x_y_z_intersections; + std::set_intersection(matches[2].begin(), matches[2].end(), + x_y_intersections.begin(), x_y_intersections.end(), + std::inserter(x_y_z_intersections, x_y_z_intersections.begin())); + + if (x_y_z_intersections.size() > 0) + return *x_y_z_intersections.begin(); + else + return points.size(); + } + + private: + const double tolerance; + + // Map from component of point to index + std::array, 3> maps; + std::vector > points; +}; + +#endif diff --git a/src/GlobalInitializer.cpp b/src/GlobalInitializer.cpp new file mode 100644 index 0000000..5999806 --- /dev/null +++ b/src/GlobalInitializer.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +#include + +#include + +#include + +GlobalInitializer::GlobalInitializer() +{ + // std::cout << "Initializing globals" << std::endl; + #ifdef INIT_RANDOM_GENERATOR + // std::cout << "Using fixed random generator generator (seed=" << INIT_RANDOM_GENERATOR << ")" << std::endl; + CGAL::default_random = CGAL::Random( INIT_RANDOM_GENERATOR ); + #endif + +} +//----------------------------------------------------------------------------- +GlobalInitializer::~GlobalInitializer() +{ + +} +//----------------------------------------------------------------------------- +GlobalInitializer _the_singleton; +GlobalInitializer& GlobalInitializer::instance() +{ + return _the_singleton; +} diff --git a/src/MeshGenerator.cpp b/src/MeshGenerator.cpp new file mode 100644 index 0000000..8a6d896 --- /dev/null +++ b/src/MeshGenerator.cpp @@ -0,0 +1,112 @@ +// Copyright (C) 2012 Anders Logg, Johannes Ring, 2012-2016 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +#include +#include +#include +#include +#include + +#include + +#include +#include + + +namespace mshr +{ + +//----------------------------------------------------------------------------- +std::shared_ptr generate_mesh(std::shared_ptr geometry, + double resolution, + std::string backend) +{ + if (resolution <= 0) + { + dolfin::dolfin_error("MeshGenerator.cpp", + "generate from CSG geometry", + "Resolution argument must be positive"); + } + + + if (geometry->dim() == 2) + { + if (backend != "cgal") + { + const std::string e = "Unknown mesh generator backend: " + backend + ". The only supported 2D backend is cgal."; + dolfin::dolfin_error("MeshGenerator.cpp", + "generate mesh of 2D geometry", + e); + } + + CSGCGALMeshGenerator2D generator; + + // Estimate the bounding circle radius from the geometry + const std::pair bounding_circle = geometry->estimate_bounding_sphere(); + const double segment_granularity = 2*bounding_circle.second/resolution; + const double cell_size = 2.0*bounding_circle.second/resolution; + + // Calculate surface representation + log(dolfin::TRACE, "Request cell size: %f", cell_size); + generator.parameters["mesh_resolution"] = -1.0; + generator.parameters["cell_size"] = cell_size; + + std::shared_ptr total_domain(new CSGCGALDomain2D(geometry, segment_granularity)); + std::vector>> subdomain_geometries; + for (const std::pair>& subdomain : geometry->get_subdomains()) + { + subdomain_geometries.push_back(std::make_pair(subdomain.first, + std::shared_ptr(new CSGCGALDomain2D(subdomain.second, segment_granularity)))); + } + return generator.generate(total_domain, subdomain_geometries); + } + else if (geometry->dim() == 3) + { + std::shared_ptr domain(new CSGCGALDomain3D(geometry)); + domain->ensure_meshing_preconditions(); + + if (backend == "cgal") + { + CSGCGALMeshGenerator3D generator; + generator.parameters["mesh_resolution"] = resolution; + return generator.generate(std::move(domain)); + } + else if (backend == "tetgen") + { + TetgenMeshGenerator3D generator; + generator.parameters["mesh_resolution"] = resolution; + return generator.generate(std::move(domain)); + } + else + { + std::string e = "Unknown mesh generator backend: " + backend; + dolfin::dolfin_error("MeshGenerator.cpp", + "Generator mesh of 3D geometry", + e); + return std::shared_ptr(); + } + } + else + { + dolfin::dolfin_error("MeshGenerator.cpp", + "create mesh from CSG geometry", + "Unhandled geometry dimension %d", geometry->dim()); + return std::shared_ptr(); + } +} + +} diff --git a/src/Meshes.cpp b/src/Meshes.cpp new file mode 100644 index 0000000..f5687cb --- /dev/null +++ b/src/Meshes.cpp @@ -0,0 +1,59 @@ +// Copyright (C) -2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +#include +#include +#include + +#include + +namespace mshr +{ + UnitSphereMesh::UnitSphereMesh(std::size_t resolution) + : dolfin::Mesh() + { + // Receive mesh according to parallel policy + if (dolfin::MPI::is_receiver(this->mpi_comm())) + { + dolfin::MeshPartitioning::build_distributed_mesh(*this); + return; + } + + std::shared_ptr s(new Sphere(dolfin::Point(0,0,0), 1.0, resolution)); + std::shared_ptr polyhedral_domain(new CSGCGALDomain3D(s)); + + TetgenMeshGenerator3D generator; + const double facet_area = 4.*DOLFIN_PI/polyhedral_domain->num_facets(); + // compute edge length assuming perfect regular tetrahedrons + const double edge_length = 1.51967*std::sqrt(facet_area); + const double max_cell_volume = std::sqrt(2.)/12.*edge_length*edge_length*edge_length; + generator.parameters["max_tet_volume"] = max_cell_volume; + generator.parameters["preserve_surface"] = true; + + std::shared_ptr mesh = generator.generate(polyhedral_domain); + // dolfin::Mesh* m = static_cast(this); + // *m = *mesh + dolfin::Mesh::operator=(*mesh); + + // Broadcast mesh according to parallel policy + if (dolfin::MPI::is_broadcaster(this->mpi_comm())) + { + dolfin::MeshPartitioning::build_distributed_mesh(*this); + return; + } + } +} diff --git a/src/OFFFileReader.cpp b/src/OFFFileReader.cpp new file mode 100644 index 0000000..87760fc --- /dev/null +++ b/src/OFFFileReader.cpp @@ -0,0 +1,188 @@ +// Copyright (C) 2015 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#include + +#include +#include +#include +#include + +#define BOOST_FILESYSTEM_NO_DEPRECATED +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +template +inline double convert_string(const std::string& s) +{ + std::istringstream is(s); + T val; + is >> val; + + return val; +} + +// get next line of file and trim away whitespace +inline void get_next_line(std::ifstream& file, std::string& line, std::size_t &lineno) +{ + do + { + std::getline(file, line); + boost::algorithm::trim(line); + lineno++; + } while ( !file.eof() && line == ""); +} +} // end anonymous namespace +//----------------------------------------------------------------------------- +namespace mshr +{ + +void OFFFileReader::read(const std::string filename, + std::vector >& vertices, + std::vector >& facets) +{ + + dolfin::log(dolfin:: TRACE, "Reading surface from %s ", filename.c_str()); + + vertices.clear(); + facets.clear(); + + typedef boost::tokenizer > tokenizer; + + std::ifstream file(filename.c_str()); + if (!file.is_open()) + { + dolfin::dolfin_error("OFFFileReader.cpp", + "open .off file to read 3D surface", + "Failed to open file"); + } + + std::string line; + std::size_t lineno = 0; + const boost::char_separator sep(" "); + + // Read the first line and trim away whitespaces + get_next_line(file, line, lineno); + + if (line != "OFF") + { + dolfin::dolfin_error("OFFFileReader.cpp", + "open .off file to read 3D surface", + "File does not start with \"OFF\" (line %u", lineno); + } + + get_next_line(file, line, lineno); + + // Read number of vertices and facets + tokenizer tokens(line, sep); + tokenizer::iterator tok_iter = tokens.begin(); + + const std::size_t num_vertices = convert_string(*tok_iter); + tok_iter++; + const std::size_t num_facets = convert_string(*tok_iter); + + vertices.reserve(num_vertices); + facets.reserve(num_facets); + + get_next_line(file, line, lineno); + + // Reader vertices + for (std::size_t i = 0; i < num_vertices; i++) + { + tokenizer tokens(line, sep); + tokenizer::iterator tok_iter = tokens.begin(); + + std::array vertex; + vertex[0] = convert_string(*tok_iter); + tok_iter++; + vertex[1] = convert_string(*tok_iter); + tok_iter++; + vertex[2] = convert_string(*tok_iter); + tok_iter++; + + dolfin_assert(tok_iter == tokens.end()); + + vertices.push_back(vertex); + get_next_line(file, line, lineno); + } + + // Read facets + for (std::size_t i = 0; i < num_facets; i++) + { + tokenizer tokens(line, sep); + tokenizer::iterator tok_iter = tokens.begin(); + + const std::size_t v = convert_string(*tok_iter); + if (v != 3) + dolfin::dolfin_error("OFFFileReader.cpp", + "reading off file", + "facet is not triangular"); + tok_iter++; + + std::array facet; + facet[0] = convert_string(*tok_iter); + tok_iter++; + facet[1] = convert_string(*tok_iter); + tok_iter++; + facet[2] = convert_string(*tok_iter); + tok_iter++; + + dolfin_assert(tok_iter == tokens.end()); + + facets.push_back(facet); + get_next_line(file, line, lineno); + } +} +//----------------------------------------------------------------------------- +void OFFFileReader::write(const std::string filename, + const std::vector >& vertices, + const std::vector >& facets) +{ + std::ofstream file(filename); + file.precision(6); + + if (!file.is_open()) + { + dolfin::dolfin_error("OFFFileReader.cpp", + "open file to write off data", + "Failed to open file"); + } + + file << "OFF " << vertices.size() << " " << facets.size() << " 0" << std::endl << std::endl; + for (const std::array& v : vertices) + { + file << v[0] << " " << v[1] << " " << v[2] << std::endl; + } + + for (const std::array& f : facets) + { + file << "3 " << f[0] << " " << f[1] << " " << f[2] << std::endl; + } +} +} diff --git a/src/Polygon_utils.h b/src/Polygon_utils.h new file mode 100644 index 0000000..3b0cfd8 --- /dev/null +++ b/src/Polygon_utils.h @@ -0,0 +1,46 @@ +// Copyright (C) 2017 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + + +#ifndef POLYGON_UTILS_H__ +#define POLYGON_UTILS_H__ + +class PolygonUtils +{ + public: + + // Computes the orientation, assuming that it is well defined + static bool ccw(const std::vector& vertices) + { + double signed_area = 0.0; + + dolfin::Point prev = vertices.back(); + for (std::vector::const_iterator it = vertices.begin(), + v_end = vertices.end(); + it != v_end; + ++it) + { + signed_area += (prev.x()*it->y())-(it->x()*prev.y()); + prev = *it; + } + + return signed_area > 0; + } +}; + + +#endif diff --git a/src/Polyhedral_multicomponent_mesh_domain_with_features_3.h b/src/Polyhedral_multicomponent_mesh_domain_with_features_3.h new file mode 100644 index 0000000..618a951 --- /dev/null +++ b/src/Polyhedral_multicomponent_mesh_domain_with_features_3.h @@ -0,0 +1,163 @@ +// Copyright (C) 2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + + +#ifndef POLYHEDRAL_MULTICOMPONENT_MESH_DOMAIN_WITH_FEATURES_3_H +#define POLYHEDRAL_MULTICOMPONENT_MESH_DOMAIN_WITH_FEATURES_3_H + +#include "FuzzyPointLocator.h" +#include "Polyhedron_utils.h" +#include + +#include +//----------------------------------------------------------------------------- +// This class reimplements Construct_initial_points (from Polyhedral_mesh_domain) +// in order to make sure that all disconnected parts of the polyhedron are +// sufficiently covered. Otherwise the meshing algorithm may miss them entirely. +template< typename IGT_ > +class Polyhedral_multicomponent_mesh_domain_with_features_3 + : public CGAL::Polyhedral_mesh_domain_with_features_3< IGT_ > +{ + public: + typedef typename CGAL::Polyhedral_mesh_domain_with_features_3< IGT_ > Base; + typedef typename Base::Polyhedron Polyhedron; + + // Passing the edge size with the constructor is a workaround. Ideally CGAL should pass it + // when calling construct_initial_points + Polyhedral_multicomponent_mesh_domain_with_features_3(const Polyhedron& p, double edge_size) + : Base(p), edge_size(edge_size) + {} + + ~Polyhedral_multicomponent_mesh_domain_with_features_3(){} + + struct Construct_initial_points + { + Construct_initial_points(const Polyhedral_multicomponent_mesh_domain_with_features_3& domain, + double edge_size) + : r_domain_(domain), edge_size(edge_size) {} + + template + OutputIterator operator()(OutputIterator pts, const int n = 8) const; + + private: + const Polyhedral_multicomponent_mesh_domain_with_features_3& r_domain_; + const double edge_size; + }; + + Construct_initial_points construct_initial_points_object() const + { + return Construct_initial_points(*this, edge_size); + } + + private : + const double edge_size; +}; +//----------------------------------------------------------------------------- +template + void recursive_insert(Set& set, + std::set& visited, + typename Polyhedron::Vertex_const_handle v, + std::size_t n) +{ + std::pair::iterator, bool> v_insert = visited.insert(v); + + // If vertex is already visited, then return + if (!v_insert.second) + { + return; + } + + // Add point to set. + set.insert_point(v->point()); + + typename Polyhedron::Halfedge_around_vertex_const_circulator start = v->vertex_begin(), current = start; + do + { + if ( set.size() >= n ) + break; + + recursive_insert(set, visited, current->opposite()->vertex(), n); + + current++; + } while (current != start); +} + +//----------------------------------------------------------------------------- +template +template +OutputIterator +Polyhedral_multicomponent_mesh_domain_with_features_3:: +Construct_initial_points::operator()(OutputIterator pts, const int n) const +{ + typedef typename Polyhedral_multicomponent_mesh_domain_with_features_3::Polyhedron Polyhedron; + typedef typename Polyhedron::Point_3 Point_3; + typedef typename Polyhedron::Vertex_const_handle Vertex_const_handle; + + const std::vector& poly_ = r_domain_.polyhedra(); + if (poly_.size() != 1) + { + dolfin::dolfin_error("Polyhedral_multicomponent_mesh_domain_with_features_3.h", + "Accessing polyhedra for domain", + "Unexpected number of polyhedrons! (%f)", poly_.size()); + } + const Polyhedron& P = poly_[0]; + std::list components; + mshr::PolyhedronUtils::get_disconnected_components(P, std::back_inserter(components)); + + // Collect inserted points in a set with a fuzzy comparison operator + // to ensure no points closer than the tolerance are inserted. + std::set visited; + + // TODO: Tune this parameter + const double tolerance = edge_size*3; + FuzzyPointMap inserted_points(tolerance); + + std::size_t current_index; + { + // get corners + std::vector > corners; + r_domain_.get_corners(std::back_inserter(corners)); + current_index = corners.size(); + current_index++; + for (const std::pair& c : corners) + { + inserted_points.forced_insert_point(c.second); + } + } + + // Insert n surface points from each disconnected component + for (Vertex_const_handle v : components) + { + recursive_insert(inserted_points, + visited, + v, + n+inserted_points.size()); + } + + // for (auto it = inserted_points.begin(); it != inserted_points.end(); it++) + for (const std::array& p : inserted_points.get_points()) + { + + *pts = std::make_pair(Point_3(p[0], p[1], p[2]), current_index); + pts++; + current_index++; + } + + return pts; +} +//----------------------------------------------------------------------------- +#endif diff --git a/src/Polyhedron_utils.h b/src/Polyhedron_utils.h new file mode 100644 index 0000000..e7e834a --- /dev/null +++ b/src/Polyhedron_utils.h @@ -0,0 +1,2975 @@ +// Copyright (C) 2014-2015 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + + +#ifndef POLYHEDRON_UTILS_H__ +#define POLYHEDRON_UTILS_H__ + +#include + +#include +#include +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#if CGAL_VERSION_NR >= 1041401000 +#include +#else +#include +#endif +#include +#include +#include +#include + +#include +#include +#include + +namespace mshr +{ + +class PolyhedronUtils +{ + public: + + //----------------------------------------------------------------------------- + template + static void dump_2D_triangulation(const CDT& cdt, std::string filename) + { + std::cout << "Dumping 2D triangulation" << std::endl; + + // Count valid cells and connected vertices + std::size_t num_cells = 0; + std::map vertex_map; + + for (typename CDT::Finite_faces_iterator cgal_cell = cdt.finite_faces_begin(); + cgal_cell != cdt.finite_faces_end(); ++cgal_cell) + { + if (cgal_cell->is_in_domain()) + { + num_cells++; + + for (std::size_t i = 0; i < 3; i++) + { + const typename CDT::Vertex_handle v = cgal_cell->vertex(i); + if (vertex_map.count(v) == 0) + { + const std::size_t s = vertex_map.size(); + vertex_map[v] = s; + } + } + } + } + + std::cout << "Adding " << vertex_map.size() << " vertices and " << num_cells << " cells" << std::endl; + + std::ofstream outfile(filename); + outfile << std::setprecision(16); + outfile << "OFF" << std::endl; + outfile << vertex_map.size() << " " << num_cells << " 0" << std::endl; + outfile << std::endl; + + std::vector vertices(vertex_map.size()); + for (const std::pair& vertex : vertex_map) + vertices[vertex.second] = vertex.first; + + for (const typename CDT::Vertex_handle v : vertices) + { + outfile << v->point()[0] << " " + << v->point()[1] << " 0 " << std::endl; + } + + for (typename CDT::Finite_faces_iterator cgal_cell = cdt.finite_faces_begin(); + cgal_cell != cdt.finite_faces_end(); ++cgal_cell) + { + // Add cell if it is in the domain + if (cgal_cell->is_in_domain()) + { + outfile << "3 " + << vertex_map[cgal_cell->vertex(0)] << " " + << vertex_map[cgal_cell->vertex(1)] << " " + << vertex_map[cgal_cell->vertex(2)] << std::endl; + } + } + } + //----------------------------------------------------------------------------- + // Scans the vertices of the polyhedron the polyhedron and returns a + // Polyhedron::Vertex_const_handle for each disconnected component. + template + static void get_disconnected_components(const Polyhedron& p, OutputIterator it) + { + //typedef Polyhedron Polyhedron_t; + typedef typename Polyhedron::Halfedge_around_vertex_const_circulator HV_const_circulator; + typedef typename Polyhedron::Vertex_const_handle Vertex_const_handle; + + // store all vertices in a set + std::set v; + for (typename Polyhedron::Vertex_const_iterator vit = p.vertices_begin(); + vit != p.vertices_end(); vit++) + v.insert(vit); + + while (!v.empty()) + { + // Add the component to the output + typename std::set::iterator start_it = v.begin(); + Vertex_const_handle start = *start_it; + + *it = start; + it++; + + // Remove all vertices belonging to component from v + std::deque queue; + queue.push_back(start); + while (!queue.empty()) + { + const Vertex_const_handle current = queue.front(); + queue.pop_front(); + + if (v.count(current) > 0) + { + v.erase(current); + + const HV_const_circulator h_start = current->vertex_begin(); + HV_const_circulator h_current = h_start; + do + { + queue.push_back(h_current->opposite()->vertex()); + h_current++; + } while (h_current != h_start); + } + } + } + } + //----------------------------------------------------------------------------- + template + static std::vector + get_holes(Polyhedron& P) + { + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + + P.normalize_border(); + + std::set border_edges; + for (typename Polyhedron::Halfedge_iterator hit = P.border_halfedges_begin(); hit != P.halfedges_end(); hit++) + { + Halfedge_handle b = hit; + dolfin_assert(b->is_border_edge()); + if (b->is_border()) + border_edges.insert(b); + else if (b->opposite()->is_border()) + border_edges.insert(b->opposite()); + else + dolfin_assert(false); + } + + std::vector border_begins; + while (!border_edges.empty()) + { + Halfedge_handle current = *(border_edges.begin()); + border_begins.push_back(current); + + std::size_t counter = 0; + const Halfedge_handle start = current; + do + { + dolfin_assert(border_edges.count(current) == 1); + border_edges.erase(current); + counter++; + current = current->next(); + } while (current != start); + } + + return std::move(border_begins); + } + //----------------------------------------------------------------------------- + // Count the number of edges in a facet or along a hole + template + static std::size_t edge_count(Halfedge_handle h) + { + Halfedge_handle current; + std::size_t counter = 0; + do + { + counter++; + current = current->next(); + } while (current != h); + + return counter; + } + //----------------------------------------------------------------------------- + template + static typename Polyhedron::Traits::Triangle_3 get_facet_triangle(typename Polyhedron::Halfedge_handle h) + { + typedef typename Polyhedron::Traits::Triangle_3 Triangle_3; + + dolfin_assert(h->facet()->is_triangle()); + + return Triangle_3(h->vertex()->point(), + h->next()->vertex()->point(), + h->next()->next()->vertex()->point()); + } + //----------------------------------------------------------------------------- + template + static double cos_min_angle(typename Polyhedron::Traits::Vector_3 v, + typename Polyhedron::Halfedge_handle h1, + typename Polyhedron::Halfedge_handle h2) + { + v /= v.squared_length(); + + double max = 0; + typename Polyhedron::Halfedge_handle current = h1; + do + { + typename Polyhedron::Traits::Vector_3 w(current->vertex()->point(), + current->next()->vertex()->point()); + w /= std::sqrt(CGAL::to_double(w.squared_length())); + max = std::max(std::abs(CGAL::to_double(w*v))); + } while (current != h2); + + return max; + } + //----------------------------------------------------------------------------- + // Given a facet and a vertex (assumed to be incident to the facet), find the + // corresponding halfedge + template + static typename Polyhedron::Halfedge_handle + find_edge(typename Polyhedron::Vertex_handle v, + typename Polyhedron::Face_handle f) + { + dolfin_assert(v != typename Polyhedron::Vertex_handle()); + dolfin_assert(f != typename Polyhedron::Face_handle()); + + typename Polyhedron::Halfedge_around_vertex_circulator start = v->vertex_begin(); + typename Polyhedron::Halfedge_around_vertex_circulator current = start; + + do + { + if (current->facet() == f) + { + dolfin_assert(current->vertex() == v && current->facet() == f); + return current; + } + + current++; + } while(current != start); + + dolfin_assert(false); + return typename Polyhedron::Halfedge_handle(); + } + //----------------------------------------------------------------------------- + template + static typename Polyhedron::Facet_handle + find_common_facet(typename Polyhedron::Vertex_handle v0, + typename Polyhedron::Vertex_handle v1, + typename Polyhedron::Vertex_handle v2) + { + typedef typename Polyhedron::Halfedge_around_vertex_circulator He_circulator; + std::set facets0; + { + He_circulator start = v0->vertex_begin(); + He_circulator current = start; + do + { + if (!current->is_border()) + facets0.insert(current->facet()); + + current++; + } while (current != start); + } + + std::set facets1; + { + He_circulator start = v1->vertex_begin(); + He_circulator current = start; + do + { + if (!current->is_border() && facets0.count(current->facet()) > 0) + facets1.insert(current->facet()); + + current++; + } while (current != start); + } + + std::set facets2; + { + He_circulator start = v2->vertex_begin(); + He_circulator current = start; + do + { + if (!current->is_border() && facets1.count(current->facet()) > 0) + facets2.insert(current->facet()); + + current++; + } while (current != start); + } + + dolfin_assert(facets2.size() < 2); + dolfin_assert(facets2.size() > 0); + + return *(facets2.begin()); + } + //----------------------------------------------------------------------------- + template + static void insert_edge(Polyhedron& P, + typename Polyhedron::Vertex_handle h, + typename Polyhedron::Vertex_handle g, + typename Polyhedron::Facet_handle f) + { + //std::pair + //edges = find_edges(h_edge, g_edge); + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + + Halfedge_handle h_edge, g_edge; + const Halfedge_handle start = g->halfedge(); + Halfedge_handle current = start; + do + { + if (current->vertex() == h) + h_edge = current; + else if(current->vertex() == g) + g_edge == current; + } while (current != start); + + dolfin_assert(h_edge != Halfedge_handle() && g_edge != Halfedge_handle()); + P.split_facet(h_edge, g_edge); + dolfin_assert(P.is_valid()); + } + //----------------------------------------------------------------------------- + template + static double get_triangle_cos_angle(Triangle_3 t1, + Triangle_3 t2) + { + typedef typename CGAL::Kernel_traits::Kernel::Vector_3 Vector_3; + + const Vector_3 v1 = t1.supporting_plane().orthogonal_vector(); + const Vector_3 v2 = t2.supporting_plane().orthogonal_vector(); + + return CGAL::to_double((v1*v2)/std::sqrt(CGAL::to_double(v1.squared_length()*v2.squared_length()))); + } + //----------------------------------------------------------------------------- + template + static double get_edge_cos_angle(typename Polyhedron::Halfedge_handle h) + { + typedef typename Polyhedron::Traits::Vector_3 Vector_3; + + // std::cout << "get edge cos theta" << std::endl; + + Vector_3 h1_vec(h->vertex()->point(), h->prev()->vertex()->point()); + h1_vec = h1_vec/std::sqrt(CGAL::to_double(h1_vec.squared_length())); + + // std::cout << "h1_vec_normalized: " << h1_vec << std::endl; + Vector_3 h2_vec(h->vertex()->point(), h->next()->vertex()->point()); + h2_vec = h2_vec/std::sqrt(CGAL::to_double(h2_vec.squared_length())); + // std::cout << "h2_vec_normalized: " << h2_vec << std::endl; + const double cos_theta = CGAL::to_double(h1_vec*h2_vec); + // std::cout << "Cos theta: " << cos_theta << std::endl; + return cos_theta; + } + //----------------------------------------------------------------------------- + // Compute the plane fit quality of the vertices from h1 to h2 both included + // Return fitting quality, max projection distance, max cos angle + template + static std::array + get_plane_fit(const typename Polyhedron::Halfedge_handle h1, + const typename Polyhedron::Halfedge_handle h2) + { + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + typedef typename Polyhedron::Traits::Plane_3 Plane_3; + typedef typename Polyhedron::Traits::Point_3 Point_3; + typedef typename Polyhedron::Traits::Vector_3 Vector_3; + typedef typename Polyhedron::Traits::FT FT; + typedef CGAL::Exact_predicates_inexact_constructions_kernel InexactKernel; + typedef typename InexactKernel::Plane_3 InexactPlane_3; + typedef typename InexactKernel::Point_3 InexactPoint_3; + //typedef typename InexactKernel::Segment_3 InexactSegment_3; + //typedef typename InexactKernel::Vector_3 InexactVector_3; + typedef typename Polyhedron::Traits::Point_3 Point_3; + + // std::cout << "Get plane fit" << std::endl; + + // std::cout << "Polygon "; + std::vector points; + //std::vector segments; + Halfedge_handle current = h1; + do + { + const Point_3& p = current->vertex()->point(); + // std::cout << ", " << p; + points.push_back(InexactPoint_3(CGAL::to_double(p[0]), + CGAL::to_double(p[1]), + CGAL::to_double(p[2]))); + current = current->next(); + } while (current != h2); + + { + const Point_3& p = h2->vertex()->point(); + // std::cout << ", " << p; + points.push_back(InexactPoint_3(CGAL::to_double(p[0]), + CGAL::to_double(p[1]), + CGAL::to_double(p[2]))); + } + + dolfin_assert(points.size() > 2); + // std::cout << "Size: " << points.size() << std::endl; + //std::cout << std::endl; + InexactPlane_3 fitting_plane_inexact; + const double fit_quality = CGAL::linear_least_squares_fitting_3(points.begin(), + points.end(), + fitting_plane_inexact, + CGAL::Dimension_tag<0>()); + Plane_3 fitting_plane(fitting_plane_inexact.a(), + fitting_plane_inexact.b(), + fitting_plane_inexact.c(), + fitting_plane_inexact.d()); + // std::cout << "Plane: " << fitting_plane << std::endl; + // std::cout << "Length of normal: " << fitting_plane.orthogonal_vector().squared_length() << std::endl; + const Vector_3 normal = fitting_plane.orthogonal_vector()/std::sqrt(CGAL::to_double(fitting_plane.orthogonal_vector().squared_length())); + + FT max_distance = (h1->vertex()->point()-fitting_plane.projection(h1->vertex()->point())).squared_length(); + FT max_angle = 0; + + Halfedge_handle prev = h1; + current = h1->next(); + do + { + const Vector_3 v = current->vertex()->point()-prev->vertex()->point(); + const FT cos_angle = v/std::sqrt(CGAL::to_double(v.squared_length())) * normal; + const Point_3 projection = fitting_plane.projection(current->vertex()->point()); + max_angle = std::max(max_angle, cos_angle); + max_distance = std::max(max_distance, (current->vertex()->point()-projection).squared_length()); + + prev = current; + current = current->next(); + } while (prev != h2); + + // std::cout << "Fit quality: " << fit_quality << ", max distance: " << max_distance << ", cos_angle: " << max_angle << std::endl; + return std::array{fit_quality, CGAL::to_double(max_distance), CGAL::to_double(max_angle)}; + // return -max_distance; + //return CGAL::to_double(fit_quality - max_angle); + } + //----------------------------------------------------------------------------- + // Compute the fit quality heuristic of the vertices from h1 to h2 both + // included. + + // Some experiences: + // * The plane fit quality as returned from + // CGAL::linear_least_squares_fitting_3() is not suitable here. It measures + // how distinct the best fitting plane is, rather than the actual quality of + // the fit. + // * The max angle between the segments and the normal work good is the hole + // has no more than one "kink". + // * The max distance from the points to their projection is also a pretty + // good measure, but needs to be normalized in some clever way if not used + // solely. + template + static double evaluate_hole_subdivision(const typename Polyhedron::Halfedge_handle h1, + const typename Polyhedron::Halfedge_handle h2) + { + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + typedef typename Polyhedron::Traits::Point_3 Point_3; + typedef CGAL::Simple_cartesian InexactKernel; + //typedef CGAL::Exact_predicates_inexact_constructions_kernel InexactKernel; + typedef typename InexactKernel::Plane_3 InexactPlane_3; + typedef typename InexactKernel::Point_3 InexactPoint_3; + typedef typename InexactKernel::Segment_3 InexactSegment_3; + typedef typename InexactKernel::Vector_3 InexactVector_3; + + // double plane1fit; + double max_angle1 = 0; + InexactPlane_3 fitting_plane1; + double avg_distance_squared1 = 0; + { + std::vector segments; + Halfedge_handle current = h1; + do + { + const Point_3& p = current->vertex()->point(); + const Point_3& next = current->next()->vertex()->point(); + segments.push_back(InexactSegment_3(InexactPoint_3(CGAL::to_double(p[0]), CGAL::to_double(p[1]), CGAL::to_double(p[2])), + InexactPoint_3(CGAL::to_double(next[0]), CGAL::to_double(next[1]), CGAL::to_double(next[2])))); + current = current->next(); + } while (current != h2); + + { + const Point_3& p = h2->vertex()->point(); + const Point_3& next = h1->vertex()->point(); + segments.push_back(InexactSegment_3(InexactPoint_3(CGAL::to_double(p[0]), CGAL::to_double(p[1]), CGAL::to_double(p[2])), + InexactPoint_3(CGAL::to_double(next[0]), CGAL::to_double(next[1]), CGAL::to_double(next[2])))); + } + + dolfin_assert(segments.size() > 2); + + /* plane1fit = */ CGAL::linear_least_squares_fitting_3(segments.begin(), + segments.end(), + fitting_plane1, + CGAL::Dimension_tag<1>()); + + // std::cout << " Plane 1: " << fitting_plane1 << ", " << plane1fit << std::endl; + + // Compute max angle between segment and fitting plane + const InexactVector_3 normal = fitting_plane1.orthogonal_vector(); + // std::cout << "Length: " << normal.squared_length() << std::endl; + dolfin_assert(dolfin::near(normal.squared_length(), 1, DOLFIN_EPS_LARGE)); + for (auto sit = segments.begin(); sit != segments.end(); sit++) + { + const InexactVector_3 v = InexactVector_3(*sit) / std::sqrt(sit->squared_length()); + // std::cout << "Length: " << (v.squared_length()-1) << std::endl; + dolfin_assert(dolfin::near(v.squared_length(), 1, DOLFIN_EPS_LARGE)); + max_angle1 = std::max(max_angle1, std::abs(v*normal)); + // max_distance_squared1 = std::max(max_distance_squared1, + avg_distance_squared1 += InexactVector_3(sit->source(), fitting_plane1.projection(sit->source())).squared_length(); + } + avg_distance_squared1 /= segments.size(); + } + + /* double plane2fit; */ + InexactPlane_3 fitting_plane2; + double max_angle2 = 0; + //double max_distance_squared2 = 0; + double avg_distance_squared2 = 0; + { + std::vector segments; + Halfedge_handle current = h2; + do + { + const Point_3& p = current->vertex()->point(); + const Point_3& next = current->next()->vertex()->point(); + segments.push_back(InexactSegment_3(InexactPoint_3(CGAL::to_double(p[0]), CGAL::to_double(p[1]), CGAL::to_double(p[2])), + InexactPoint_3(CGAL::to_double(next[0]), CGAL::to_double(next[1]), CGAL::to_double(next[2])))); + current = current->next(); + } while (current != h1); + + const Point_3& p = h1->vertex()->point(); + const Point_3& next = h2->vertex()->point(); + + segments.push_back(InexactSegment_3(InexactPoint_3(CGAL::to_double(p[0]), CGAL::to_double(p[1]), CGAL::to_double(p[2])), + InexactPoint_3(CGAL::to_double(next[0]), CGAL::to_double(next[1]), CGAL::to_double(next[2])))); + + dolfin_assert(segments.size() > 2); + // std::cout << " Size: " << segments.size() << std::endl; + + /* plane2fit = */ CGAL::linear_least_squares_fitting_3(segments.begin(), + segments.end(), + fitting_plane2, + CGAL::Dimension_tag<1>()); + + // std::cout << " Plane 1: " << fitting_plane1 << ", " << plane1fit << std::endl; + + // Compute max angle between plane and segments + const InexactVector_3 normal = fitting_plane2.orthogonal_vector(); + dolfin_assert(dolfin::near(normal.squared_length(), 1, DOLFIN_EPS_LARGE)); + for (auto sit = segments.begin(); sit != segments.end(); sit++) + { + const InexactVector_3 v = InexactVector_3(*sit) / std::sqrt(sit->squared_length()); + dolfin_assert(dolfin::near(v.squared_length(), 1, DOLFIN_EPS_LARGE)); + max_angle2 = std::max(max_angle2, std::abs(v*normal)); + /* max_distance_squared2 = std::max(max_distance_squared2, */ + /* InexactVector_3(sit->source(), fitting_plane2.projection(sit->source())).squared_length()); */ + avg_distance_squared2 += InexactVector_3(sit->source(), fitting_plane2.projection(sit->source())).squared_length(); + } + avg_distance_squared2 /= segments.size(); + } + + // const double cos_angle = fitting_plane1.orthogonal_vector()*fitting_plane2.orthogonal_vector(); + // std::cout << " Angle: " << cos_angle << "(" << acos(cos_angle)/(2*DOLFIN_PI)*360 << ")" << std::endl; + // std::cout << "Max angles: " << max_angle1 << "(" << acos(max_angle1)/(2*DOLFIN_PI)*360 << "), " << max_angle2 << " (" << acos(max_angle2)/(2*DOLFIN_PI)*360 << ")" << std::endl; + // return std::min(plane1fit, plane2fit); // - .04*cos_angle - .05*max_angle1 - .05*max_angle2;// + triangulation_extra; + + //return (-avg_distance_squared1 - avg_distance_squared2)/std::max(max_angle1, max_angle2); ///std::min(plane1fit, plane2fit); + return -std::max(max_angle1, max_angle2); + } + + //----------------------------------------------------------------------------- + // Compute the transformation that rotates a given vector (assumed to be of + // unit length) into (0,0,1) + template + static CGAL::Aff_transformation_3::Kernel> + rotate_to_xy(Vector_3 a) + { + typedef typename CGAL::Kernel_traits::Kernel::RT RT; + typedef typename CGAL::Aff_transformation_3::Kernel> Aff_transformation_3; + + // Inner product of a and target vector (0,0,1) + const RT cos_theta = a[2]; + + // Cross product of a and target vector (0,0,1) + // const RT sine_theta = CGAL::sqrt(a[1]*a[1] + a[0]*a[0]); + const RT sine_theta = sqrt(CGAL::to_double(a[1]*a[1] + a[0]*a[0])); + + const RT ux = a[1]/sine_theta;; + const RT uy = -a[0]/sine_theta; + const RT uz = 0; + dolfin_assert(CGAL::abs(ux*ux + uy*uy + uz*uz) - 1 < DOLFIN_EPS); + + return Aff_transformation_3( + cos_theta+ux*ux*(1-cos_theta), ux*uy*(1-cos_theta)-uz*sine_theta, ux*uz*(1-cos_theta)+uy*sine_theta, + uy*ux*(1-cos_theta)+uz*sine_theta, cos_theta+uy*uy*(1-cos_theta), uy*uz*(1-cos_theta)-ux*sine_theta, + uz*ux*(1-cos_theta)-uy*sine_theta, uz*uy*(1-cos_theta)+ux*sine_theta, cos_theta+uz*uz*(1-cos_theta)); + } + //----------------------------------------------------------------------------- + template + class Add2DTriangulation : public CGAL::Modifier_base + { + public: + Add2DTriangulation(const CDT& cdt, + const typename HDS::Traits::Aff_transformation_3& from_xy, + const typename CDT::Vertex_handle v0, + const typename CDT::Vertex_handle v1, + const typename HDS::Traits::Vector_3 displacement=typename HDS::Traits::Vector_3(CGAL::Null_vector())) + : cdt(cdt), + rotate_from_xy(from_xy), + displacement(displacement), + v0_2d(v0), + v1_2d(v1), + v0_initialized(false), + v1_initialized(false) + { } + + void operator()(HDS& hds) + { + CGAL::Polyhedron_incremental_builder_3 builder(hds, true); + + for (typename CDT::Finite_vertices_iterator cgal_vertex = cdt.finite_vertices_begin(); + cgal_vertex != cdt.finite_vertices_end(); ++cgal_vertex) + { + cgal_vertex->info() = std::make_pair(typename HDS::Vertex_handle(), + std::numeric_limits::max()); + } + + // Find the face incident to both v0 and v1 + // We will use this to check the orientation of the faces when they are + // inserted into the 3D polyhedron + typename CDT::Face_handle f; + + // Count valid cells and connected vertices + std::size_t num_cells = 0; + std::size_t num_vertices = 0; + for (typename CDT::Finite_faces_iterator cgal_cell = cdt.finite_faces_begin(); + cgal_cell != cdt.finite_faces_end(); ++cgal_cell) + { + if (cgal_cell->is_in_domain()) + { + num_cells++; + + for (std::size_t i = 0; i < 3; i++) + { + typename CDT::Vertex_handle v = cgal_cell->vertex(i); + if (v->info().second == std::numeric_limits::max()) + { + v->info().second = num_vertices; + num_vertices++; + } + } + + if (cgal_cell->has_vertex(v0_2d) && cgal_cell->has_vertex(v1_2d)) + { + std::cout << "!!!! Found the face!!!" << std::endl; + std::cout << cgal_cell->index(v0_2d) << " " << cgal_cell->index(v1_2d) << std::endl; + f = cgal_cell; + } + } + } + + // Check the orientation of the facets + const bool flip = ((f->index(v1_2d)+1)%3 == f->index(v0_2d)); + + const typename HDS::Traits::Vector_3 displacement_flipped = flip ? -displacement : displacement; + + builder.begin_surface(num_vertices, num_cells); + + std::cout << "Adding " << num_vertices << " vertices and " << num_cells << " cells" << std::endl; + + // Add vertices + std::size_t vertex_index = 0; + for (typename CDT::Finite_vertices_iterator cgal_vertex = cdt.finite_vertices_begin(); + cgal_vertex != cdt.finite_vertices_end(); ++cgal_vertex) + { + const typename CDT::Vertex_handle current = cgal_vertex; + + // Transform point from xy plane to where it belongs in the polyhendron (2D point is EPICK) + typename HDS::Traits::Point_3 p = rotate_from_xy(typename HDS::Traits::Point_3(cgal_vertex->point()[0], + cgal_vertex->point()[1], + 0))+displacement; + + /* std::cout << " 2D point: " << cgal_vertex->point() << " (" << z << ")" << std::endl; */ + /* std::cout << " Rotated: " << p << std::endl; */ + /* std::cout << " Index: " << vertex_index << std::endl; */ +// FRom remove-null-facets + /* // std::cout << "Now creating face" << std::endl; */ + + /* // This typedef for some reason gives a "unused local typedef" warning */ + /* //from clang (...?) --> write out the typename in the statements */ + /* //below. */ + /* // typedef typename Halfedge::Base HBase; */ + /* edges[0]->Halfedge::Base::set_next(edges[1]); */ + /* decorator.set_prev(edges[1], edges[0]); */ + /* edges[1]->Halfedge::Base::set_next(edges[2]); */ + /* decorator.set_prev(edges[2], edges[1]); */ + /* edges[2]->Halfedge::Base::set_next(edges[0]); */ + /* decorator.set_prev(edges[0], edges[2]); */ +// end remove-null-facets + + // Add vertex (convert point to EPECK) + typename HDS::Vertex_handle h = builder.add_vertex(p); + if (current == v0_2d) + { + v0 = h; + v0_initialized = true; + } + else if (current == v1_2d) + { + v1 = h; + v1_initialized = true; + } + + // Attach index to vertex and increment + cgal_vertex->info().second = vertex_index++; + } + + // Add cells to mesh and build domain marker mesh function + // std::size_t cell_index = 0; + for (typename CDT::Finite_faces_iterator cgal_cell = cdt.finite_faces_begin(); + cgal_cell != cdt.finite_faces_end(); ++cgal_cell) + { + // Add cell if it is in the domain + if (cgal_cell->is_in_domain()) + { + const typename CDT::Geom_traits::Triangle_2 t_2d(cgal_cell->vertex(0)->point(), + cgal_cell->vertex(1)->point(), + cgal_cell->vertex(2)->point()); + + // Transform point from xy plane to where it belongs in the polyhendron (2D point is EPICK) + typename HDS::Traits::Point_3 p0 = rotate_from_xy(typename HDS::Traits::Point_3(cgal_cell->vertex(0)->point()[0], + cgal_cell->vertex(0)->point()[1], + 0)); + + typename HDS::Traits::Point_3 p1 = rotate_from_xy(typename HDS::Traits::Point_3(cgal_cell->vertex(1)->point()[0], + cgal_cell->vertex(1)->point()[1], + 0)); + + typename HDS::Traits::Point_3 p2 = rotate_from_xy(typename HDS::Traits::Point_3(cgal_cell->vertex(2)->point()[0], + cgal_cell->vertex(2)->point()[1], + 0)); + + typename HDS::Traits::Triangle_3 t_3d(p0, p1, p2); + + const double diff = sqrt(CGAL::to_double(t_3d.squared_area()))-CGAL::to_double(CGAL::abs(t_2d.area())); + if (std::abs(diff) > 1e-10) + std::cout << "Rotated triangle differs: " << diff << std::endl; + + + + builder.begin_facet(); + builder.add_vertex_to_facet(cgal_cell->vertex(0)->info().second); + if (flip) + { + builder.add_vertex_to_facet(cgal_cell->vertex(2)->info().second); + builder.add_vertex_to_facet(cgal_cell->vertex(1)->info().second); + /* std::cout << " Adding vertex: (" << cgal_cell->vertex(0)->info().second */ + /* << ", " << cgal_cell->vertex(2)->info().second */ + /* << ", " << cgal_cell->vertex(1)->info().second << std::endl; */ + } + else + { + builder.add_vertex_to_facet(cgal_cell->vertex(1)->info().second); + builder.add_vertex_to_facet(cgal_cell->vertex(2)->info().second); + /* std::cout << " Adding vertex: (" << cgal_cell->vertex(0)->info().second */ + /* << ", " << cgal_cell->vertex(1)->info().second */ + /* << ", " << cgal_cell->vertex(2)->info().second << std::endl; */ + + } + new_facets.insert(builder.end_facet()); + } + } + + builder.end_surface(); + + dolfin_assert(v0_initialized); + dolfin_assert(v1_initialized); + } + const CDT& cdt; + const typename HDS::Traits::Aff_transformation_3 rotate_from_xy; + const typename HDS::Traits::Vector_3 displacement; + const typename CDT::Vertex_handle v0_2d, v1_2d; + typename HDS::Vertex_handle v0, v1; + bool v0_initialized, v1_initialized; + std::set new_facets; + }; + + /// Attempts to triangulate a polygon in 3d by projecting vertices into the + /// best fitting plane and triangulating in 2d. + /// Return the new added edges. + /// If the triangulation is not possible (the boundary self intersects in this 2d plane) then + /// the return vector is empty + template + static bool triangulate_polygon_3d(Polyhedron& P, + const typename Polyhedron::Halfedge_handle h, + bool check_for_intersections = true, + bool refine = true) + { + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + typedef typename Polyhedron::Vertex_handle Vertex_handle; + // typedef typename Polyhedron::Facet_handle Facet_handle; + typedef typename Polyhedron::Traits::FT FT; + typedef typename Polyhedron::Traits::Point_3 Point_3; + typedef typename Polyhedron::Traits::Segment_3 Segment_3; + typedef typename Polyhedron::Traits::Vector_3 Vector_3; + typedef CGAL::Aff_transformation_3 Aff_transformation_3; + + + typedef CGAL::Exact_predicates_inexact_constructions_kernel InexactKernel; + typedef typename InexactKernel::Plane_3 InexactPlane_3; + typedef typename InexactKernel::Point_3 InexactPoint_3; + typedef typename InexactKernel::Vector_3 InexactVector_3; + typedef typename InexactKernel::Point_2 InexactPoint_2; + // typedef typename InexactKernel::Segment_2 InexactSegment_2; + typedef typename InexactKernel::Segment_3 InexactSegment_3; + typedef typename InexactKernel::Triangle_2 InexactTriangle_2; + + typedef CGAL::Triangulation_vertex_base_with_info_2, InexactKernel> Vb; + typedef CGAL::Delaunay_mesh_face_base_2 Fb; + typedef CGAL::Triangulation_data_structure_2 TDS; + typedef CGAL::No_intersection_tag Itag; + typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; + typedef CGAL::Delaunay_mesh_size_criteria_2 Mesh_criteria_2; + typedef CGAL::Delaunay_mesher_no_edge_refinement_2 CGAL_Mesher_2; + + std::cout << "Triangulating hole as 2d polygon" << std::endl; + dolfin_assert(P.is_valid()); + + { + std::cout << "Polygon "; + typename Polyhedron::Halfedge_handle current = h; + do + { + std::cout << current->vertex()->point() << ", "; + current = current->next(); + } while (current != h); + + std::cout << std::endl; + } + + Aff_transformation_3 to_xy; + Vector_3 plane_normal; + { + // Compute the best fitting plane of the points of the hole + InexactPlane_3 fitting_plane; + + + std::vector boundary; + Halfedge_handle current = h; + do + { + const Point_3& p = current->vertex()->point(); + const Point_3& next = current->next()->vertex()->point(); + boundary.push_back(InexactSegment_3(InexactPoint_3(CGAL::to_double(p[0]), CGAL::to_double(p[1]), CGAL::to_double(p[2])), + InexactPoint_3(CGAL::to_double(next[0]), CGAL::to_double(next[1]), CGAL::to_double(next[2])))); + + current = current->next(); + } while (current != h); + + /* const double fit_quality = */ + CGAL::linear_least_squares_fitting_3(boundary.begin(), + boundary.end(), + fitting_plane, + CGAL::Dimension_tag<1>()); + + // Compute rotation that will rotate the fitting plane to the xy plane + const InexactVector_3 orthogonal = fitting_plane.orthogonal_vector(); + const Vector_3 orthogonal_exact(orthogonal[0], orthogonal[1], orthogonal[2]); + // std::cout << "Orthogonal vector: " << orthogonal << ", " << orthogonal.squared_length() << std::endl; + const Aff_transformation_3 rotation = rotate_to_xy(orthogonal_exact); + // std::cout << "Rotation: " << rotation << std::endl; + std::cout << "Rotated: " << rotation.transform(orthogonal_exact) << std::endl; + const InexactPoint_3 point_on_plane = fitting_plane.point(); + const FT z = rotation.transform(Point_3(point_on_plane[0], + point_on_plane[1], + point_on_plane[2]))[2]; + + const Aff_transformation_3 zero_z(CGAL::Translation(), Vector_3(0,0,-z)); + + double max_cos_normal_angle = 0; + const InexactVector_3 normal = fitting_plane.orthogonal_vector(); + std::cout << "Normal: " << plane_normal << std::endl; + plane_normal = Vector_3(normal.x(), normal.y(), normal.z()); + + double max_squared_distance = 0; + dolfin_assert(dolfin::near(normal.squared_length(), 1, DOLFIN_EPS_LARGE)); + + // Compute 2D bounding box + std::vector points_2D; + for (const InexactSegment_3& s : boundary) + { + const Point_3 current_exact(s.source()[0], s.source()[1], s.source()[2]); + const Point_3 rotated = rotation.transform(current_exact); + points_2D.push_back(InexactPoint_2(CGAL::to_double(rotated[0]), + CGAL::to_double(rotated[1]))); + } + CGAL::Bbox_2 bbox = CGAL::bbox_2(points_2D.begin(), points_2D.end()); + std::cout << "Bounding box: " << bbox << std::endl; + + // InexactVector_3 prev = InexactVector_3(boundary[boundary.size()-1]) / std::sqrt(boundary[boundary.size()-1].squared_length()); + for (const InexactSegment_3& s : boundary) + { + InexactVector_3 current = InexactVector_3(s) / std::sqrt(s.squared_length()); + max_squared_distance = std::max(max_squared_distance, (s.source()-fitting_plane.projection(s.source())).squared_length()); + + // std::cout << "Length: " << sit->squared_length() << ", " << current.squared_length() << ", " << (current*normal) << std::endl; + dolfin_assert(dolfin::near(current.squared_length(), 1, DOLFIN_EPS_LARGE)); + max_cos_normal_angle = std::max(max_cos_normal_angle, CGAL::abs(current*normal)); + // prev = current; + + const Point_3 current_exact(s.source()[0], s.source()[1], s.source()[2]); + const Point_3 rotated = rotation.transform(current_exact); + // bbox += InexactPoint_2(CGAL::to_double(rotated[0]), CGAL::to_double(rotated[1])).bbox(); + // std::cout << " " << bbox << std::endl; + } + + std::cout << "Max abs cos normal angle: " << max_cos_normal_angle << std::endl; + // std::cout << "Plane quality: " << fit_quality << std::endl; + std::cout << "Max distance: " << std::sqrt(max_squared_distance) << std::endl; + + if (max_cos_normal_angle > .2) + { + std::cout << "ERROR: Rejecting 2d triangulating, max_cos_normal_angle: " << max_cos_normal_angle << std::endl; + return false; + } + if (max_squared_distance > 1e-8) + { + std::cout << "ERROR: Rejecting 2d triangulating, max squared_distance: " << max_squared_distance << std::endl; + return false; + } + + const Aff_transformation_3 normalization(CGAL::Translation(), Vector_3(-(bbox.xmax()+bbox.xmin())/2, + -(bbox.ymax()+bbox.ymin())/2, + 0)); + + to_xy = normalization*zero_z*rotation; + } + + + // std::cout << "Rotate normal: " << rotation.transform(fitting_plane.orthogonal_vector()) << std::endl; + // dolfin_assert(dolfin::near(fitting_plane.orthogonal_vector().squared_length(), 1, DOLFIN_EPS_LARGE)); + double max_z = 0.; + + CDT cdt; + + std::cout << "Projected polygon" << std::endl; + std::cout << "Polygon"; + + // Insert vertices into 2D triangulation + std::vector vertices; + double max_squared_edge_length = 0; + double min_squared_edge_length = std::numeric_limits::max(); + + { + Halfedge_handle current = h; + Point_3 prev = current->prev()->vertex()->point(); + std::stringstream ss; + ss << "Polygon "; + do + { + + const Point_3& p = current->vertex()->point(); + // InexactPoint_3 p_inexact(CGAL::to_double(p[0]), +// CGAL::to_double(p[1]), + // CGAL::to_double(p[2])); + // InexactPoint_3 p_projected = fitting_plane.projection(p_inexact); + // std::cout << " " << p_projected << ", "; + + const double length_current = CGAL::to_double(Segment_3(prev, p).squared_length()); + max_squared_edge_length = std::max(max_squared_edge_length, length_current); + min_squared_edge_length = std::min(min_squared_edge_length, length_current); + const Point_3 rotated = to_xy.transform(p); + + max_z = std::max(max_z, CGAL::to_double(CGAL::abs(rotated.z()))); + ss << " " << rotated << ", "; + + const InexactPoint_2 p_2d(CGAL::to_double(rotated[0]), CGAL::to_double(rotated[1])); + + // std::cout << " " << p_2d << ", "; + + vertices.push_back(cdt.insert(p_2d)); + vertices.back()->info().first = current->vertex(); + + prev = p; + current = current->next(); + } while (current != h); + + // std::cout << std::endl; + std::cout << std::endl; + std::cout << ss.str() << std::endl; + } + + std::cout << "Size of points: " << vertices.size() << std::endl; + // std::cout << "z = " << z << std::endl; + std::cout << "Max z : " << max_z << std::endl; + std::cout << "Longest edge: " << max_squared_edge_length << std::endl; + std::cout << "Shortest edge: " << min_squared_edge_length << std::endl; + + + // Check if any of the edges intersect (before actually adding the + // constrained edges to the triangulation) +#if 0 + if (check_for_intersections) + { + for (std::size_t i = 0; i < vertices.size()-1; i++) + { + const Point_3& a = vertices[i]->info().first->point(), b = vertices[+1]->info().first->point(); + const InexactSegment_2 s(vertices[i]->point(), vertices[i+1]->point()); + const Segment_3 original(a, b); + + const InexactSegment_3 s2(fitting_plane.projection(InexactPoint_3(CGAL::to_double(a[0]), + CGAL::to_double(a[1]), + CGAL::to_double(a[2]))), + fitting_plane.projection(InexactPoint_3(CGAL::to_double(b[0]), + CGAL::to_double(b[1]), + CGAL::to_double(b[2])))); + + for (std::size_t j = i+1; j < vertices.size(); j++) + { + InexactSegment_2 s2(vertices[j]->point(), vertices[(j+1)%vertices.size()]->point()); + + const auto intersection = CGAL::intersection(s, s2); + + if (intersection) + { + if (boost::get(&*intersection)) + { + if (j != i+1 && i != (j+1)%vertices.size()) + { + std::cout << "Non-neighbors (" << i << ", " << j << ")/" << vertices.size() + << " intersect in single point" << std::endl; + + return false; + } + } + else if (boost::get(&*intersection)) + { + std::cout << "Intersects in segment" << std::endl; + return false; + } + else + { + dolfin_assert(false); + return false; + } + } // end if intersection + } // end inner loop + } // end outer loop + + // No edges intersect, so we can safely insert then as constraints to the + // triangulation + } +#endif + + // Insert the edges around the facet as constraints to the triangulation + for (std::size_t i = 0; i < vertices.size(); i++) + { + // std::cout << "Insert constraint: (" << i << ") " << vertices[i]->point() << ", (" << (i+1)%vertices.size() << ") " << vertices[(i+1)%vertices.size()]->point() << std::endl; + cdt.insert_constraint(vertices[i], vertices[(i+1)%vertices.size()]); + } + + // std::cout << "Done triangulating" << std::endl; + // std::cout << "Num vertices: " << cdt.number_of_vertices() << std::endl; + + if (refine) + { + // Create mesher + CGAL_Mesher_2 mesher(cdt); + + // Set shape and size criteria + mesher.set_criteria(Mesh_criteria_2(.125, 2*std::sqrt(max_squared_edge_length))); + + // No size criteria, only shape + //mesher.set_criteria(Mesh_criteria_2()); + + std::cout << "Max edge length: " << 2*std::sqrt(max_squared_edge_length) << std::endl; + + // Refine CGAL mesh/triangulation + std::cout << "Refining 2D mesh" << std::endl; + mesher.refine_mesh(); + } + else + { + for (auto f = cdt.finite_faces_begin(); f != cdt.finite_faces_end(); f++) + { + f->set_in_domain(true); + } + } + + std::cout << "Done meshing. Num vertices: " << cdt.number_of_vertices() << std::endl; + + { + double shortest_edge = std::numeric_limits::max(); + double smallest_triangle = std::numeric_limits::max(); + for (auto f = cdt.finite_faces_begin(); f != cdt.finite_faces_end(); f++) + { + if (f->is_in_domain()) + { + for (int i = 0; i < 3; i++) + shortest_edge = std::min(shortest_edge, + CGAL::to_double((f->vertex(i)->point()-f->vertex((i+1)%3)->point()).squared_length())); + InexactTriangle_2 t(f->vertex(0)->point(), + f->vertex(1)->point(), + f->vertex(2)->point()); + smallest_triangle = std::min(smallest_triangle, CGAL::to_double(CGAL::abs(t.area()))); + } + } + + std::cout << "Shortest edge in 2D triangulation: " << shortest_edge << std::endl; + std::cout << "Smallest triangle in 2D triangulation: " << smallest_triangle << std::endl; + } + + //P.normalize_border(); + // dolfin_assert(P.is_valid(false, 1)); + + // add the triangulation to the polyhedron + + dump_2D_triangulation(cdt, "triangulation2D.off"); + + Add2DTriangulation builder(cdt, + to_xy.inverse(), + vertices[0], + vertices[1], + plane_normal*.001); + P.delegate(builder); + + std::cout << "Inserted: " << builder.new_facets.size() << " new facets" << std::endl; + + // Find the border edge incident to "inserted h" + const typename Polyhedron::Halfedge_around_vertex_circulator start = builder.v0->vertex_begin(); + typename Polyhedron::Halfedge_around_vertex_circulator current = start; + + do + { + if (current->opposite()->vertex() == builder.v1) + { + std::cout << "Found the edge" << std::endl; + break; + } + std::cout << "Advancing" << std::endl; + current++; + } while (current != start); + + const typename Polyhedron::Halfedge_handle h_new = current->is_border() ? current : current->opposite(); + + if (!h_new->is_border()) + { + std::cout << "Is border edge: " << h_new->is_border_edge() << std::endl; + dolfin::dolfin_error("Polyhedron_utils.h", + "Locating border edge", + "locating border edge"); + } + + std::cout << "2D: " << vertices[0]->point() << std::endl; + std::cout << "Original: " << h->vertex()->point() << std::endl; + std::cout << "Original Next: " << h->next()->vertex()->point() << std::endl; + std::cout << "Original prev: " << h->prev()->vertex()->point() << std::endl; + std::cout << "Inserted: " << h_new->vertex()->point() << std::endl; + std::cout << "Inserted next: " << h_new->next()->vertex()->point() << std::endl; + std::cout << "Inserted prev: " << h_new->prev()->vertex()->point() << std::endl; + std::cout << "Vertex degree: " << h_new->vertex()->vertex_degree() << std::endl; + + + std::cout << "Joining loop" << std::endl; + + { + Halfedge_handle a = h_new; + Halfedge_handle b = h; + double max_distance = 0.; + do + { + max_distance = std::max(max_distance, CGAL::to_double((a->vertex()->point()-b->vertex()->point()).squared_length())); + + a = a->next(); + b = b->prev(); + } while (a != h_new); + std::cout << "Max distance between merged vertices: " << max_distance << std::endl; + } + + { + double shortest_edge = std::numeric_limits::max(); + for (auto e = P.halfedges_begin(); e != P.halfedges_end(); e++) + { + shortest_edge = std::min(shortest_edge, CGAL::to_double((e->vertex()->point()-e->opposite()->vertex()->point()).squared_length())); + } + + std::cout << "Shortest edge: " << shortest_edge << std::endl; + } + + { + double smallest_triangle = std::numeric_limits::max();; + for (auto f = P.facets_begin(); f != P.facets_end(); f++) + { + auto h = f->halfedge(); + if (f->is_triangle()) + { + typename Polyhedron::Traits::Triangle_3 t(h->vertex()->point(), + h->next()->vertex()->point(), + h->next()->next()->vertex()->point()); + smallest_triangle = std::min(smallest_triangle, + CGAL::to_double(t.squared_area())); + } + } + std::cout << "Smallest triangle before join: " << smallest_triangle << std::endl; + } + + + // + P.join_loop(h, h_new); + + { + double shortest_edge = std::numeric_limits::max(); + for (auto e = P.halfedges_begin(); e != P.halfedges_end(); e++) + { + shortest_edge = std::min(shortest_edge, CGAL::to_double((e->vertex()->point()-e->opposite()->vertex()->point()).squared_length())); + } + + std::cout << "Shortest edge after merge: " << shortest_edge << std::endl; + } + + { + double smallest_triangle = std::numeric_limits::max();; + for (auto f = P.facets_begin(); f != P.facets_end(); f++) + { + auto h = f->halfedge(); + if (f->is_triangle()) + { + typename Polyhedron::Traits::Triangle_3 t(h->vertex()->point(), + h->next()->vertex()->point(), + h->next()->next()->vertex()->point()); + smallest_triangle = std::min(smallest_triangle, + CGAL::to_double(t.squared_area())); + } + } + std::cout << "Smallest triangle: " << smallest_triangle << std::endl; + } + + + + std::cout << "H degree: " << h->facet()->facet_degree() << std::endl; + std::cout << "Hole: " << (h->is_border_edge() ? "Yes" : "No") << std::endl; + + return true; + } + //----------------------------------------------------------------------------- + template + static std::pair evaluate_planarity(const typename Polyhedron::Halfedge_handle from, + const typename Polyhedron::Halfedge_handle to) + { + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + typedef CGAL::Exact_predicates_inexact_constructions_kernel InexactKernel; + typedef typename InexactKernel::Plane_3 InexactPlane_3; + typedef typename InexactKernel::Segment_3 InexactSegment_3; + typedef typename InexactKernel::Vector_3 InexactVector_3; + typedef typename InexactKernel::Point_3 InexactPoint_3; + typedef typename Polyhedron::Traits::Point_3 Point_3; + + std::vector segments; + Halfedge_handle current = from; + while (current != to) + { + const Point_3& s1 = current->vertex()->point(); + const Point_3& s2 = current->next()->vertex()->point(); + segments.push_back(InexactSegment_3(InexactPoint_3(CGAL::to_double(s1[0]), + CGAL::to_double(s1[1]), + CGAL::to_double(s1[2])), + InexactPoint_3(CGAL::to_double(s2[0]), + CGAL::to_double(s2[1]), + CGAL::to_double(s2[2])))); + current = current->next(); + } + + InexactPlane_3 fitting_plane; + CGAL::linear_least_squares_fitting_3(segments.begin(), + segments.end(), + fitting_plane, + CGAL::Dimension_tag<1>()); + + const InexactVector_3 orthogonal_vector = fitting_plane.orthogonal_vector()/sqrt(CGAL::to_double(fitting_plane.orthogonal_vector().squared_length())); + + dolfin_assert(dolfin::near(orthogonal_vector.squared_length(), 1., DOLFIN_EPS_LARGE)); + + double max_distance = 0.; + double min_cos_angle = 1.; + + current = from;; + while (current != to) + { + const Point_3& p1 = current->vertex()->point(); + const InexactPoint_3 p1_inexact(CGAL::to_double(p1[0]), + CGAL::to_double(p1[1]), + CGAL::to_double(p1[2])); + + const Point_3& next = current->next()->vertex()->point(); + const InexactPoint_3 next_inexact(CGAL::to_double(next[0]), + CGAL::to_double(next[1]), + CGAL::to_double(next[2])); + + const InexactVector_3 current_vector = (p1_inexact-next_inexact)/sqrt(CGAL::to_double((p1_inexact-next_inexact).squared_length())); + dolfin_assert(dolfin::near(current_vector.squared_length(), 1., DOLFIN_EPS_LARGE)); + + // Check if the angle between this vector and the fitting plane is acceptable + min_cos_angle = std::min(min_cos_angle, + CGAL::abs(current_vector*orthogonal_vector)); + + max_distance = std::max(max_distance, + sqrt((p1_inexact-fitting_plane.projection(p1_inexact)).squared_length())); + + current = current->next(); + } + + return std::make_pair(max_distance, min_cos_angle); + } + //----------------------------------------------------------------------------- + template + static std::pair + find_best_cut (Polyhedron& P, + const typename Polyhedron::Halfedge_handle h, + double cos_angle_tolerance) + { + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + typedef typename Polyhedron::Traits::Point_3 Point_3; + typedef CGAL::Exact_predicates_inexact_constructions_kernel InexactKernel; + typedef typename InexactKernel::Plane_3 InexactPlane_3; + typedef typename InexactKernel::Point_3 InexactPoint_3; + typedef typename InexactKernel::Vector_3 InexactVector_3; + typedef typename InexactKernel::Segment_3 InexactSegment_3; + + Halfedge_handle h1_distance, h2_distance; + double min_distance = std::numeric_limits::max(); + + Halfedge_handle h1_angle, h2_angle; + double min_cos_normal = std::numeric_limits::max(); + + Halfedge_handle current1 = h->next()->next(); + const Halfedge_handle current1_end = h->prev()->prev(); + do + { + // std::cout << "Current1 " << current1->vertex()->point() << std::endl; + Halfedge_handle current2 = current1->next()->next(); + const Halfedge_handle current2_end = current1->prev(); + do + { + /* std::cout << " Current 2" << std::endl; */ + /* std::cout << "Segment " << current1->vertex()->point() << ", " << current2->vertex()->point() << std::endl; */ + + double max_distance_local = 0.; + double max_cos_normal_local = 0; + + // Compute fitting plane of segments from current1 to current2 + // std::cout << "Compute fit plane 1" << std::endl; + { + InexactPlane_3 fitting_plane; + std::vector segments; + Halfedge_handle current3 = current1; + while (current3 != current2) + { + const Point_3& s1 = current3->vertex()->point(); + const Point_3& s2 = current3->next()->vertex()->point(); + segments.push_back(InexactSegment_3(InexactPoint_3(CGAL::to_double(s1[0]), + CGAL::to_double(s1[1]), + CGAL::to_double(s1[2])), + InexactPoint_3(CGAL::to_double(s2[0]), + CGAL::to_double(s2[1]), + CGAL::to_double(s2[2])))); + current3 = current3->next(); + } + + /* const double fit_quality = */ + CGAL::linear_least_squares_fitting_3(segments.begin(), + segments.end(), + fitting_plane, + CGAL::Dimension_tag<1>()); + const InexactVector_3 orthogonal_vector = fitting_plane.orthogonal_vector()/sqrt(CGAL::to_double(fitting_plane.orthogonal_vector().squared_length())); + + // std::cout << "Plane vector length: " << (CGAL::abs(orthogonal_vector.squared_length())-1) << std::endl; + dolfin_assert(dolfin::near(orthogonal_vector.squared_length(), 1., DOLFIN_EPS_LARGE)); + + current3 = current1; + while (current3 != current2) + { + const Point_3& p1 = current3->vertex()->point(); + const InexactPoint_3 p1_inexact(CGAL::to_double(p1[0]), + CGAL::to_double(p1[1]), + CGAL::to_double(p1[2])); + + const Point_3& next = current3->next()->vertex()->point(); + const InexactPoint_3 next_inexact(CGAL::to_double(next[0]), + CGAL::to_double(next[1]), + CGAL::to_double(next[2])); + + const InexactVector_3 current_vector = (p1_inexact-next_inexact)/sqrt(CGAL::to_double((p1_inexact-next_inexact).squared_length())); + dolfin_assert(dolfin::near(current_vector.squared_length(), 1., DOLFIN_EPS_LARGE)); + + // Check if angle between this vector and the fitting plane is acceptable + max_cos_normal_local = std::max(max_cos_normal_local, CGAL::abs(current_vector*orthogonal_vector)); + + max_distance_local = std::max(max_distance_local, + CGAL::to_double((p1_inexact-fitting_plane.projection(p1_inexact)).squared_length())); + + current3 = current3->next(); + } + } + + // std::cout << "Done evaluating candidate: " << std::endl; + + // Compute fitting plane of segments from current2 to current1 + { + InexactPlane_3 fitting_plane; + std::vector segments; + Halfedge_handle current3 = current2; + while (current3 != current1) + { + const Point_3& s1 = current3->vertex()->point(); + const Point_3& s2 = current3->next()->vertex()->point(); + segments.push_back(InexactSegment_3(InexactPoint_3(CGAL::to_double(s1[0]), + CGAL::to_double(s1[1]), + CGAL::to_double(s1[2])), + InexactPoint_3(CGAL::to_double(s2[0]), + CGAL::to_double(s2[1]), + CGAL::to_double(s2[2])))); + current3 = current3->next(); + } + + /* const double fit_quality = */ CGAL::linear_least_squares_fitting_3(segments.begin(), + segments.end(), + fitting_plane, + CGAL::Dimension_tag<1>()); + + const InexactVector_3 orthogonal_vector = fitting_plane.orthogonal_vector()/sqrt(CGAL::to_double(fitting_plane.orthogonal_vector().squared_length())); + // const double angle_tolerance = .5; + // std::cout << "Plane vector length: " << (CGAL::abs(orthogonal_vector.squared_length())-1) << std::endl; + dolfin_assert(dolfin::near(CGAL::to_double(orthogonal_vector.squared_length()), 1., DOLFIN_EPS_LARGE)); + + current3 = current2; + while (current3 != current1) + { + const Point_3& p1 = current3->vertex()->point(); + const InexactPoint_3 p1_inexact(CGAL::to_double(p1[0]), + CGAL::to_double(p1[1]), + CGAL::to_double(p1[2])); + + const Point_3& next = current3->next()->vertex()->point(); + const InexactPoint_3 next_inexact(CGAL::to_double(next[0]), + CGAL::to_double(next[1]), + CGAL::to_double(next[2])); + + const InexactVector_3 current_vector = (p1_inexact-next_inexact)/sqrt(CGAL::to_double((p1_inexact-next_inexact).squared_length())); + dolfin_assert(dolfin::near(CGAL::to_double(current_vector.squared_length()), 1., DOLFIN_EPS_LARGE)); + + // Check if the angle between this vector and the fitting plane is acceptable + max_cos_normal_local = std::max(max_cos_normal_local, CGAL::abs(current_vector*orthogonal_vector)); + + max_distance_local = std::max(max_distance_local, + CGAL::to_double((p1_inexact-fitting_plane.projection(p1_inexact)).squared_length())); + + current3 = current3->next(); + } + } + + // std::cout << "Done evaluating other half: " << std::endl; + + if (max_distance_local < min_distance) + { + // std::cout << "New min split" << std::endl; + min_distance = max_distance_local; + h1_distance = current1; + h2_distance = current2; + } + + if (max_cos_normal_local < min_cos_normal) + { + min_cos_normal = max_cos_normal_local; + h1_angle = current1; + h2_angle = current2; + } + + current2 = current2->next(); + } while (current2 != current2_end); + + current1 = current1->next(); + } while (current1 != current1_end); + + std::cout << "Best cut, distance=" << min_distance << " : Segment " << h1_distance->vertex()->point() << ", " << h2_distance->vertex()->point() << std::endl; + std::cout << "Best cut, angle=" << min_cos_normal << " : Segment " << h1_angle->vertex()->point() << ", " << h2_angle->vertex()->point() << std::endl; + + + return std::make_pair(h1_angle, h2_angle); + } + //----------------------------------------------------------------------------- + template + static bool split_hole_planar(Polyhedron& P, + typename Polyhedron::Halfedge_handle h) + { + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + typedef typename Polyhedron::Traits::Vector_3 Vector_3; + + std::cout << "Split hole" << std::endl; + + const std::pair best_cut = find_best_cut(P, h, .3); + + std::cout << "Found best cut" << std::endl; + if (best_cut.first == Halfedge_handle() || best_cut.second == Halfedge_handle()) + return false; + + + const std::pair planarity1 = evaluate_planarity(best_cut.first, best_cut.second); + const std::pair planarity2 = evaluate_planarity(best_cut.second, best_cut.first); + + std::cout << "Planarity: " << planarity1.first << " vs " << planarity2.first << std::endl; + + const Halfedge_handle start = planarity1.first < planarity2.first ? best_cut.first : best_cut.second; + const Halfedge_handle end = planarity1.first < planarity2.first ? best_cut.second : best_cut.first; + + // Compute average edge length + double length = 0.; + int num_edges = 0; + Halfedge_handle current = best_cut.first; + do + { + length += sqrt(CGAL::to_double((current->vertex()->point()-current->next()->vertex()->point()).squared_length())); + num_edges++; + } while (current != best_cut.first); + + length /= num_edges; + + const Vector_3 cut = start->vertex()->point() - end->vertex()->point(); + const double cut_length = sqrt(CGAL::to_double(cut.squared_length())); + const int num_new_edges = static_cast(cut_length/length); + + P.fill_hole(h); + + Halfedge_handle new_diagonal = P.split_facet(end, start); + // Now new_diagonal is on the facet to be + + std::cout << "Inserting " << num_new_edges << " vertices from " << start->vertex()->point() << " to " << end->vertex()->point() << std::endl; + for (int i = 1; i < num_new_edges; i++) + { + Halfedge_handle hnew = P.split_edge(new_diagonal); + hnew->vertex()->point() = end->vertex()->point() + cut*i/num_new_edges; + // std::cout << " " << i << ": " << hnew->vertex()->point() << (hnew->vertex()->point()-hnew->opposite()->vertex()->point()).squared_length() << std::endl; + } + + /* Halfedge_handle c = new_diagonal; */ + /* do */ + /* { */ + /* std::cout << "Edge: " << c->vertex()->point() << " <--> " << c->opposite()->vertex()->point() << " : " << (c->vertex()->point()-c->opposite()->vertex()->point()).squared_length() << std::endl; */ + /* c = c->next(); */ + /* } while (c != new_diagonal); */ + + + const Halfedge_handle opposite = new_diagonal->opposite(); + P.make_hole(new_diagonal); + const bool success = triangulate_polygon_3d(P, new_diagonal); + //dolfin_assert(success); + + P.make_hole(opposite); + if (!success) + return false; + + return true; + } + //----------------------------------------------------------------------------- + + /* template */ + /* void min_vertex_degree(const Polyhedron& p) */ + /* { */ + /* std::size_t min_degree = std::numeric_limits::max(); */ + /* std::size_t min_degree_non_border = min_degree; */ + + /* for (typename Polyhedron::Vertex_const_iterator vit = p.vertices_begin(); */ + /* vit != p.vertices_end(); vit++) */ + /* { */ + /* min_degree = std::min(min_degree, vit->vertex_degree); */ + + /* } */ + + /* std::cout << "Min vertex_degree: " << min_degree << std::endl; */ + /* } */ + //----------------------------------------------------------------------------- + template + static void list_hole(const typename Polyhedron::Halfedge_handle h) + { + std::size_t counter = 0; + std::cout << "Polygon"; + + { + typename Polyhedron::Halfedge_handle current = h; + do + { + counter++; + current = current->next(); + } while(current != h); + } + + // if (counter < 250) + // { + typename Polyhedron::Halfedge_handle current = h; + do + { + std::cout << " " << current->vertex()->point() << ","; + + current = current->next(); + } while(current != h); + // } + std::cout << std::endl; + + // std::cout << " size: " << counter << std::endl; + } + //----------------------------------------------------------------------------- + template + static std::string print_triangle(typename Polyhedron::Halfedge_handle h) + { + std::stringstream ss; + ss << "Triangle " + << h->prev()->vertex()->point() << ", " + << h->vertex()->point() << ", " + << h->next()->vertex()->point() << std::endl; + ss << "Area: " << triangle_area(h) << std::endl; + return ss.str(); + } + //----------------------------------------------------------------------------- + template + static double triangle_area(typename Polyhedron::Halfedge_handle h) + { + typedef typename Polyhedron::Traits::Triangle_3 Triangle_3; + + Triangle_3 t(h->prev()->vertex()->point(), + h->vertex()->point(), + h->next()->vertex()->point()); + + return CGAL::to_double(t.squared_area()); + } + //----------------------------------------------------------------------------- + template + static typename Polyhedron::Vertex_handle get_common_vertex(typename Polyhedron::Facet_handle f1, + typename Polyhedron::Facet_handle f2) + { + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + typedef typename Polyhedron::Vertex_handle Vertex_handle; + + // Find common vertex + Halfedge_handle h1 = f1->halfedge(); + Halfedge_handle current1 = h1; + do + { + Halfedge_handle h2 = f2->halfedge(); + Halfedge_handle current2 = h2; + do + { + if (current2->vertex() == current1->vertex()) + return current2->vertex(); + + current2 = current2->next(); + } while (h2 != current2); + + current1 = current1->next(); + } while (h1 != current1); + + return Vertex_handle(); + } + //----------------------------------------------------------------------------- + template + static double facet_angle(typename Polyhedron::Halfedge_handle h) + { + dolfin_assert(h->is_border()); + dolfin_assert(h->next()->is_border()); + + typedef typename Polyhedron::Traits::Line_3 Line_3; + typedef typename Polyhedron::Traits::Vector_3 Vector_3; + typedef typename Polyhedron::Traits::Point_3 Point_3; + + const Line_3 l(h->prev()->vertex()->point(), + h->vertex()->point()); + + const Point_3& p11 = h->next()->vertex()->point(); + const Point_3 p12 = l.projection(p11); + const Vector_3 v1(p12, p11); + + dolfin_assert(h->opposite()->facet()->is_triangle()); + + const Point_3& p21 = h->opposite()->next()->vertex()->point(); + const Point_3 p22 = l.projection(p21); + const Vector_3 v2(p22, p21); + + return CGAL::to_double((v1*v2)/std::sqrt(CGAL::to_double(v1.squared_length()*v2.squared_length()))); + } + //----------------------------------------------------------------------------- + template + static bool segment_intersects_triangle(const Segment_3& s, + const typename CGAL::Kernel_traits::Kernel::Triangle_3& t) + { + typedef typename CGAL::Kernel_traits::Kernel::Point_3 Point_3; + + auto result = CGAL::intersection(s, t); + if (!result) + return false; + + if (const Point_3* p = boost::get(&*result)) + { + if (*p == t[0] || *p == t[1] || *p == t[2]) + return false; + else + return true; + } + else if (const Segment_3* s_ = boost::get(&*result)) + { + if ( (s.source() == t[0] || s.source() == t[1] || s.source() == t[2]) && + (s.target() == t[0] || s.target() == t[1] || s.target() == t[2]) ) + return false; + else + return true; + } + + dolfin_assert(false); + return false; + } + //----------------------------------------------------------------------------- + // Check if two triangles intersect. + // Neighbor triangles (share a vertice or an edge) do not intersect + // if t1 == t2 (geometrically), the triangles do not intersect + template + static bool triangles_intersect(const Triangle_3& t1, const Triangle_3& t2) + { + typedef typename CGAL::Kernel_traits::Kernel::Point_3 Point_3; + typedef typename CGAL::Kernel_traits::Kernel::Segment_3 Segment_3; + + if ( (t1[0] == t2[0] || t1[0] == t2[1] || t1[0] == t2[2]) && + (t1[1] == t2[0] || t1[1] == t2[1] || t1[1] == t2[2]) && + (t1[2] == t2[0] || t1[2] == t2[1] || t1[2] == t2[2])) + return false; + + auto result = CGAL::intersection(t1, t2); + + if (!result) + return false; + + if (const Point_3* p = boost::get(&*result)) + { + if (t1[0] == t2[0] || t1[0] == t2[1] || t1[0] == t2[2] || + t1[1] == t2[0] || t1[1] == t2[1] || t1[1] == t2[2] || + t1[2] == t2[0] || t1[2] == t2[1] || t1[2] == t2[2]) + return false; + else + return true; + } + else if (const Segment_3* s = boost::get(&*result)) + { + std::size_t common_vertices = 0; + if (t1[0] == t2[0] || t1[0] == t2[1] || t1[0] == t2[2]) + common_vertices++; + + if (t1[1] == t2[0] || t1[1] == t2[1] || t1[1] == t2[2]) + common_vertices++; + + if (t1[2] == t2[0] || t1[2] == t2[1] || t1[2] == t2[2]) + common_vertices++; + + if (common_vertices > 1) + return false; + else + return true; + } + else if (const Triangle_3* t = boost::get(&*result)) + return true; + else if (const std::vector* v = boost::get >(&*result)) + return true; + + dolfin_assert(false); + return false; + } + //----------------------------------------------------------------------------- + template + static bool triangle_set_intersects(const std::vector& t) + { + for (std::size_t i = 0; i < t.size(); i++) + { + for (std::size_t j = i+1; j < t.size(); j++) + { + if (triangles_intersect(t[i], t[j])) + return true; + } + } + return false; + } + //----------------------------------------------------------------------------- + template + static std::size_t vertex_count_halfedges(Halfedge_handle h) + { + std::size_t count = 0; + Halfedge_handle current = h; + do + { + ++count; + current = current->next()->opposite(); + } while (current != h); + + dolfin_assert(count > 0); + return count; + } + //----------------------------------------------------------------------------- + template + static std::size_t total_vertex_count_halfedges(const Polyhedron& P, std::map& m) + { + std::size_t total_count = 0; + for (typename Polyhedron::Vertex_const_iterator it = P.vertices_begin(); it != P.vertices_end(); it++) + { + const std::size_t c = vertex_count_halfedges(it->halfedge()); + m[it] = c; + // std::cout << " Halfedge count: " << c << std::endl; + if (c == 0) + {int tmp; std::cin >> tmp; } + total_count += c; + } + return total_count; + } + //----------------------------------------------------------------------------- + template + static bool halfedge_is_in_polyhedron(const Polyhedron& P, + typename Polyhedron::Halfedge_const_handle h) + { + for (auto hit = P.halfedges_begin(); hit != P.halfedges_end(); hit++) + { + if (hit == h) + return true; + } + return false; + } + //----------------------------------------------------------------------------- + template + static bool vertex_is_in_polyhedron(const Polyhedron& P, + typename Polyhedron::Vertex_const_handle v) + { + for (auto vit = P.vertices_begin(); vit != P.vertices_end(); ++vit) + { + if (vit == v) + return true; + } + + return false; + } + //----------------------------------------------------------------------------- + template + static bool check_vertex_consistency(const Polyhedron& P) + { + // std::cout << "Checking vertex consistency" << std::endl; + std::size_t counter = 0; + + // Build a set of the list of vertices for faster lookup + std::set vertex_set; + for (auto vit = P.vertices_begin(); vit != P.vertices_end(); ++vit) + { + dolfin_assert(vertex_set.count(vit) == 0); + vertex_set.insert(vit); + } + + std::deque queue; + std::set visited; + + queue.push_back(P.halfedges_begin()); + while (!queue.empty()) + { + typename Polyhedron::Halfedge_const_handle current = queue.back(); + queue.pop_back(); + if (visited.count(current) == 0) + { + counter++; + typename Polyhedron::Halfedge_const_handle start = current; + visited.insert(current); + + // Walk around the facet (or hole). Check halfedges and queue opposites + do + { + if (vertex_set.count(current->vertex()) == 0) + { + // std::cout << "Vertex not in vertex list: " << current->vertex()->point() << std::endl; + return false; + } + + // TODO: Add the opposite check: All vertices should be reachable via halfedges + queue.push_back(current->opposite()); + current = current->next(); + } while(current != start); + } + } + + // std::cout << " Checked " << counter << " halfedges" << std::endl; + return true; + } + //----------------------------------------------------------------------------- + template + static bool triangle_set_intersect_triangle(Triangle_3 t, + const std::vector& triangle_set) + { + for (auto tit = triangle_set.begin(); tit != triangle_set.end(); tit++) + { + if (triangles_intersect(*tit, t)) + return true; + } + return false; + } + //----------------------------------------------------------------------------- + template + static bool segment_intersects_triangle_set(const Segment_3& s, + const std::vector::Kernel::Triangle_3>& triangle_set) + { + for (auto tit = triangle_set.begin(); tit != triangle_set.end(); tit++) + { + if (segment_intersects_triangle(s, *tit)) + return true; + } + return false; + } + //----------------------------------------------------------------------------- + template + static bool center_vertex_triangulation(Polyhedron& P, + const typename Polyhedron::Halfedge_handle h) + { + typedef typename Polyhedron::Traits::Point_3 Point_3; + typedef typename Polyhedron::Traits::Triangle_3 Triangle_3; + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + + // check if the triangulation with a center vertex intersects any of the + // neighbor triangles + // std::cout << "Attempting center vertex triangulation" << std::endl; + dolfin_assert(P.is_valid(false, 0)); + dolfin_assert(halfedge_is_in_polyhedron(P, h)); + dolfin_assert(vertex_is_in_polyhedron(P, h->vertex())); + + const std::array plane_fit = get_plane_fit(h, h->prev()); + if (plane_fit[0] < .85) + { + // std::cout << " Rejected. Not sufficiently planar: " << plane_fit[0] << std::endl; + return false; + } + + // Collect set of neighbor triangles and compute centroid + std::vector triangles; + Point_3 centroid = CGAL::ORIGIN; + std::size_t counter = 0; + { + Halfedge_handle current = h; + do + { + if (!current->opposite()->is_border() && current->opposite()->facet()->facet_degree() == 3) + { + triangles.push_back(get_facet_triangle(current->opposite())); + } + centroid = centroid + (current->vertex()->point()-CGAL::ORIGIN); + counter++; + + current = current->next(); + } while (current != h); + } + // std::cout << "Number of triangles: " << triangles.size() << std::endl; + centroid = CGAL::ORIGIN + (centroid-CGAL::ORIGIN)/counter; + // std::cout << "Centroid: " << centroid << std::endl; + + Halfedge_handle current = h; + do + { + Triangle_3 current_triangle(current->vertex()->point(), current->next()->vertex()->point(), centroid); + for (auto tit = triangles.begin(); tit != triangles.end(); tit++) + { + if (triangles_intersect(current_triangle, *tit)) + { + // std::cout << "No: Triangle " << current_triangle[0] << " " << current_triangle[1] << " " << current_triangle[2] << std::endl; + // std::cout << "Triangle " << (*tit)[0] << " " << (*tit)[1] << " " << (*tit)[2] << std::endl; + return false; + } + } + + triangles.push_back(current_triangle); + + current = current->next(); + } while (current != h); + + // std::cout << "Facet degree before center vertex: " << h->facet()->facet_degree() << std::endl; + + P.normalize_border(); + dolfin_assert(P.is_valid(false, 1)); + dolfin_assert(!h->is_border_edge()); + dolfin_assert(h->facet()->facet_degree() > 3); + + Halfedge_handle center = P.create_center_vertex(h); + /* Halfedge_handle g = h->next()->next(); */ + /* dolfin_assert(check_vertex_consistency(P)); */ + /* dolfin_assert(halfedge_is_in_polyhedron(P, h)); */ + /* dolfin_assert(vertex_is_in_polyhedron(P, h->vertex())); */ + /* dolfin_assert(vertex_is_in_polyhedron(P, g->vertex())); */ + /* std::cout << "Splitting: Segment " << h->vertex()->point() << ", " << g->vertex()->point() << std::endl; */ + /* std::cout << "Vertex degrees: " << h->vertex()->vertex_degree() << ", " << g->vertex()->vertex_degree() << std::endl; */ + /* std::cout << "My count: " << vertex_count_halfedges(h) << ", " << vertex_count_halfedges(g) << std::endl; */ + + /* std::cout << "Facet degree: " << h->facet()->facet_degree() << std::endl; */ + /* std::cout << "Opposite facet degree: " << h->opposite()->facet()->facet_degree() << std::endl; */ + /* std::cout << "Num halfedges: " << P.size_of_halfedges() << std::endl; */ + /* std::map vertex_degrees; */ + /* std::cout << "My total count: " << total_vertex_count_halfedges(P, vertex_degrees); */ + /* std::cout << "Mapped: " << vertex_degrees.at(h->vertex()) << ", " << vertex_degrees.at(g->vertex()) << std::endl; */ + /* std::cout << "Size of map: " << vertex_degrees.size() << std::endl; */ + /* std::cout << "-- Splitting facet --" << std::endl; */ + + /* Halfedge_handle diagonal = P.split_facet(h, g); */ + + /* std::cout << "Vertex degrees: " << h->vertex()->vertex_degree() << ", " << g->vertex()->vertex_degree() << std::endl; */ + /* std::cout << "My count: " << vertex_count_halfedges(h) << ", " << vertex_count_halfedges(g) << std::endl; */ + /* std::map vertex_degrees_after; */ + /* std::cout << "My total count: " << total_vertex_count_halfedges(P, vertex_degrees_after) << std::endl; */ + /* std::cout << "Size of map: " << vertex_degrees_after.size() << std::endl; */ + /* std::cout << "Num halfedges: " << P.size_of_halfedges() << std::endl; */ + /* std::cout << "Mapped: " << vertex_degrees_after.at(h->vertex()) << ", " << vertex_degrees_after.at(g->vertex()) << std::endl; */ + /* for (auto it = vertex_degrees_after.begin(); it != vertex_degrees_after.end(); ++it) */ + /* { */ + /* if (vertex_degrees.at(it->first) != it->second) */ + /* { */ + /* std::cout << "DIFF!!!" << vertex_degrees[it->first] << " " << it->second << std::endl; */ + /* } */ + + /* if (vertex_degrees.at(it->first) == 0 || it->second == 0) */ + /* { */ + /* std::cout << "zero degree " << it->first->point() << std::endl; */ + /* } */ + /* } */ + /* dolfin_assert(P.is_valid(false)); */ + /* Halfedge_handle c = diagonal->vertex()->halfedge(); */ + /* Halfedge_handle start = c; */ + /* do */ + /* { */ + /* if (c->opposite()->vertex() == diagonal->opposite()->vertex()) */ + /* std::cout << "Yes!!!" << std::endl; */ + /* c = c->opposite()->next(); */ + /* } while (c != start); */ + /* dolfin_assert(P.is_valid(false, 0)); */ + /* std::cout << "Splitting edge: Segment " << diagonal->opposite()->vertex()->point() << ", " << diagonal->vertex()->point() << std::endl; */ + /* Halfedge_handle center = P.split_edge(diagonal); */ + center->vertex()->point() = centroid; + dolfin_assert(P.is_valid(false, 0)); + + /* std::cout << "Splitting facet: " << diagonal->opposite()->vertex()->point() << ", " << diagonal->opposite()->prev()->prev()->vertex()->point() << std::endl; */ + /* P.split_facet(diagonal->opposite(), diagonal->opposite()->prev()->prev()); */ + + /* do */ + /* { */ + /* dolfin_assert(P.is_valid(false, 0)); */ + /* std::cout << "adding diagonal: " << diagonal->next()->vertex()->point() << ", " << center->vertex()->point() << std::endl; */ + /* diagonal = P.split_facet(diagonal->next(), center); */ + /* diagonal = diagonal->opposite(); */ + + /* } while (diagonal->next()->next() != center); */ + /* std::cout << "Center vertex degree: " << center->vertex()->vertex_degree() << std::endl; */ + /* //P.normalize_border(); */ + /* { */ + /* std::ofstream ofile("center-vertex.off"); */ + /* ofile << P; */ + /* } */ + + /* dolfin_assert(P.is_valid()); */ + + return true; + } + //----------------------------------------------------------------------------- + template + static void close_hole(Polyhedron& P, + typename Polyhedron::Halfedge_handle h) + { + //typedef typename Polyhedron::Traits::Point_3 Point_3; + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + + dolfin_assert(P.is_valid(false, 0)); + dolfin_assert(P.is_pure_triangle()); + dolfin_assert(h->is_border()); + + P.fill_hole(h); + P.normalize_border(); + + dolfin_assert(h->facet()->facet_degree() > 2); + + // Since the facet may be split, we push the facets to a fifo queue. + // Neighbor facets are now not guaranteed to be triangles. + std::deque queue; + queue.push_back(h); + + while (!queue.empty()) + { + // std::cout << "--- Popping facet from queue (" << queue.size() << ")" << std::endl; + const Halfedge_handle current = queue.front(); + queue.pop_front(); + + // list_hole(current); + + dolfin_assert(P.is_valid(false, 0)); + dolfin_assert(halfedge_is_in_polyhedron(P, current)); + + if (current->facet()->facet_degree() == 3) + { + //P.fill_hole(h); + dolfin_assert(current->opposite()->facet()->facet_degree() != 3 || + !triangles_intersect(get_facet_triangle(current), + get_facet_triangle(current->opposite()))); + dolfin_assert(current->next()->opposite()->facet()->facet_degree() != 3 || + !triangles_intersect(get_facet_triangle(current), + get_facet_triangle(current->next()->opposite()))); + dolfin_assert(current->prev()->opposite()->facet()->facet_degree() != 3 || + !triangles_intersect(get_facet_triangle(current), + get_facet_triangle(current->prev()->opposite()))); + } + else + { + // std::cout << "Attempting to triangulate in 2D" << std::endl; + dolfin_assert(halfedge_is_in_polyhedron(P, current)); + if (!triangulate_polygon_3d(P, current, false)) + { + dolfin_assert(halfedge_is_in_polyhedron(P, current)); + + Halfedge_handle facet = subdivide_facet(P, current); + + queue.push_back(facet->opposite()); + queue.push_back(facet); + } + } + } + } + //----------------------------------------------------------------------------- + template + static typename Polyhedron::Halfedge_handle + subdivide_facet(Polyhedron& P, typename Polyhedron::Halfedge_handle h) + { + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + typedef typename Polyhedron::Traits::Triangle_3 Triangle_3; + typedef typename Polyhedron::Traits::Point_3 Point_3; + typedef typename Polyhedron::Traits::Segment_3 Segment_3; + typedef typename Polyhedron::Traits::Vector_3 Vector_3; + + // Search for segments that divide the hole, such that the dividing segment + // does not intersect triangles next to the hole and the facets on each side + // of the split is as planar as possible. + + // Store all triangles around the hole and compute max edge length + std::vector border_triangles; + double max_squared_edge_length = 0; + { + Halfedge_handle current = h; + do + { + max_squared_edge_length = std::max(max_squared_edge_length, + CGAL::to_double(Segment_3(current->prev()->vertex()->point(), + current->vertex()->point()).squared_length())); + if (current->opposite()->facet()->facet_degree() == 3) + { + border_triangles.push_back(get_facet_triangle(current->opposite())); + } + + current = current->next(); + } while (current != h); + } + + dolfin_assert(border_triangles.size() > 4); + + // Search for the best dividing segment + double best_quality = -1000; + Halfedge_handle best_outer; + Halfedge_handle best_inner; + + Halfedge_handle current_outer = h; + + // FIXME: This loop should run to h->prev()->prev(), but need + // handle the specially + const Halfedge_handle outer_end = h->prev()->prev(); + do + { + Halfedge_handle current_inner = current_outer->next()->next(); + const Halfedge_handle inner_end = h; + do + { + if (current_inner->next() != current_outer && + current_inner->prev() != current_outer) + { + + Segment_3 current_segment(current_outer->vertex()->point(), + current_inner->vertex()->point()); + + // Check that this does not introduce an intersection + if (!segment_intersects_triangle_set(current_segment, border_triangles)) + { + const double candidate_quality = evaluate_hole_subdivision(current_inner, current_outer); + + if (candidate_quality > best_quality) + { + best_outer = current_outer; + best_inner = current_inner; + best_quality = candidate_quality; + } + } + } + + current_inner = current_inner->next(); + } while (current_inner != inner_end); + + current_outer = current_outer->next(); + } while (current_outer != outer_end); + + dolfin_assert(best_outer != Halfedge_handle()); + dolfin_assert(best_inner != Halfedge_handle()); + + // std::cout << "Found best subdivision: " << std::endl; + + // list_hole(best_outer); + // std::cout << "Segment " << best_outer->vertex()->point() + // << ", " << best_inner->vertex()->point() << std::endl; + // std::cout << "Quality: " << best_quality << std::endl; + + dolfin_assert(P.is_valid(false, 0)); + const Halfedge_handle new_diagonal = P.split_facet(best_inner, best_outer); + const Point_3& p = new_diagonal->opposite()->vertex()->point(); + + const Vector_3 new_edge(new_diagonal->opposite()->vertex()->point(), + new_diagonal->vertex()->point()); + + const int num_segments = static_cast(sqrt(CGAL::to_double(new_edge.squared_length())/max_squared_edge_length)+.5); + + // Note: Don't use std::size_t as 0-1 becomes very large... + for (int i = 1; i < num_segments; i++) + { + //std::cout << "Splitting segment" << std::endl; + Halfedge_handle new_segment = P.split_edge(new_diagonal); + new_segment->vertex()->point() = p + static_cast(i)/num_segments * new_edge; + } + + P.normalize_border(); + dolfin_assert(P.is_valid(false, 0)); + + return new_diagonal; + } + //----------------------------------------------------------------------------- + /* template */ + /* static double evaluate_heuristic(const Polyhedron& P, */ + /* typename Polyhedron::Halfedge_handle h, */ + /* double plane_fit) */ + /* { */ + /* typedef typename Polyhedron::Traits::Triangle_3 Triangle_3; */ + /* typedef typename Polyhedron::Traits::Vector_3 Vector_3; */ + /* typedef CGAL::Exact_predicates_inexact_constructions_kernel InexactKernel; */ + /* typedef typename InexactKernel::Plane_3 InexactPlane_3; */ + + + /* // const double distance_to_plane_weight = 1.0; */ + /* const double planarity_weight = 1.0; */ + /* const double dihedral_weight = 1.0; */ + /* const double ear_angle_weight = 1.0; */ + + /* // Compute the planarity of the points excluding the current point */ + /* InexactPlane_3 p; */ + /* const double plane_fit_quality = get_plane_fit(h->next(), */ + /* h->prev(), */ + /* &p); */ + /* // Compute the maximum of the dihedral angle to the neighbors */ + /* const Triangle_3 candidate_triangle(h->prev()->vertex()->point(), */ + /* h->vertex()->point(), */ + /* h->next()->vertex()->point()); */ + /* const double cos_dihedral = (std::min(get_triangle_cos_angle(candidate_triangle, */ + /* get_facet_triangle(h->opposite())), */ + /* get_triangle_cos_angle(candidate_triangle, */ + /* get_facet_triangle(h->next()->opposite())))+1)/2; */ + + /* // Compute the angle of the cutted ear */ + /* const Vector_3 v1(h->vertex()->point(), */ + /* h->prev()->vertex()->point()); */ + /* const Vector_3 v2(h->vertex()->point(), */ + /* h->next()->vertex()->point()); */ + + /* const double cos_ear_angle = (CGAL::to_double((v1*v2)/std::sqrt(CGAL::to_double(v1.squared_length()*v2.squared_length())))+1)/2.0; */ + /* const double ear_angle_quality = cos_ear_angle; */ + + /* std::cout << "Triangle " << candidate_triangle[0] */ + /* << ", " << candidate_triangle[1] */ + /* << "," << candidate_triangle[2] << std::endl; */ + + /* std::cout << "Evaluate: planarity: " << (plane_fit_quality/plane_fit) */ + /* << ", dihedral: " << cos_dihedral */ + /* << ", ear angle: " << ear_angle_quality << std::endl; */ + + /* return planarity_weight*plane_fit_quality + dihedral_weight*cos_dihedral + ear_angle_quality*ear_angle_weight; */ + + /* /\* return planarity_weight*plane_fit_quality/plane_fit + *\/ */ + /* /\* dihedral_weight*cos_dihedral + *\/ */ + /* /\* (1-std::exp(-ear_angle_weight*cos_ear_angle))*cos_dihedral; *\/ */ + /* } */ + //----------------------------------------------------------------------------- + template + static bool vertex_is_border(typename Polyhedron::Vertex_const_handle v) + { + //typename Polyhedron::Vertex::Halfedge_around_vertex_circulator h_start = v->vertex_begin(); + auto h_start = v->vertex_begin(); + auto h_current = h_start; + do + { + if (h_current->is_border_edge()) + return true; + h_current++; + } while(h_current != h_start); + + return false; + } + //----------------------------------------------------------------------------- + template + static bool facets_are_neighbors(typename Polyhedron::Facet_handle f1, + typename Polyhedron::Facet_handle f2) + { + typename Polyhedron::Halfedge_handle h1 = f1->halfedge(); + typename Polyhedron::Halfedge_handle start1 = h1; + do + { + typename Polyhedron::Halfedge_handle h2 = f2->halfedge(); + typename Polyhedron::Halfedge_handle start2 = h2; + do + { + if (h2->vertex() == h1->vertex()) + return true; + + h2 = h2->next(); + } while (h2 != start2); + + h1 = h1->next(); + } while (h1 != start1); + + return false; + } + //----------------------------------------------------------------------------- + template + static bool segment_intersects_facets(typename Polyhedron::Vertex_handle v1, + typename Polyhedron::Vertex_handle v2) + { + typedef typename Polyhedron::Halfedge_around_vertex_circulator Vertex_circulator; + typedef typename Polyhedron::Traits::Segment_3 Segment_3; + typedef typename Polyhedron::Traits::Triangle_3 Triangle_3; + typedef typename Polyhedron::Traits::Point_3 Point_3; + + Segment_3 s(v1->point(), v2->point()); + + Vertex_circulator start = v1->vertex_begin(); + Vertex_circulator current = start; + do + { + if (!current->is_border() && current->facet()->is_triangle()) + { + Triangle_3 t = get_facet_triangle(current); + auto result = CGAL::intersection(t, s); + dolfin_assert(result); + + if (const Point_3* p = boost::get(&*result)) + { + dolfin_assert(*p == s.source() || *p == s.target()); + } + else if (const Segment_3* s = boost::get(&*result)) + { + return true; + } + else + { + dolfin_assert(false); + } + } + + current++; + } while (start != current); + + return false; + } + //----------------------------------------------------------------------------- + template + static std::string list_self_intersections(Polyhedron& p) + { + typedef typename Polyhedron::Facet_handle Facet_handle; + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + typedef typename Polyhedron::Traits Polyhedron_traits; + typedef typename Polyhedron_traits::Triangle_3 Triangle_3; + typedef typename Polyhedron_traits::Point_3 Point_3; + typedef typename Polyhedron_traits::Segment_3 Segment_3; + + std::stringstream ss; + + std::vector > intersections; + CGAL::Polygon_mesh_processing::self_intersections(p, std::back_inserter(intersections)); + + for (const std::pair& iit : intersections) + { + ss << "Intersection (neighbors: " << (facets_are_neighbors(iit.first, iit.second) ? "Yes" : "No") << ")" << std::endl; + const Halfedge_handle h1 = iit.first->halfedge(); + const Halfedge_handle h2 = iit.second->halfedge(); + print_triangle(h1); + print_triangle(h2); + + // Compute intersection + + const Triangle_3 t1(h1->vertex()->point(), + h1->next()->vertex()->point(), + h1->next()->next()->vertex()->point()); + + const Triangle_3 t2(h2->vertex()->point(), + h2->next()->vertex()->point(), + h2->next()->next()->vertex()->point()); + + const auto result = CGAL::intersection(t1, t2); + + dolfin_assert(result); + if (const Segment_3* s = boost::get(&*result)) + { + ss << "Segment: " << *s << std::endl; + } + else if (const Point_3* p = boost::get(&*result)) + { + ss << "Point: " << *p << std::endl; + } + else if (const Triangle_3* t = boost::get(&*result)) + { + ss << "Triangle: " << *t << std::endl; + } + else + { + ss << "Polygon" << std::endl; + } + } + + return ss.str(); + } + //----------------------------------------------------------------------------- + template + static double closest_vertices(const Polyhedron& p) + { + // TODO: Use a better optimal algorithm for closest pair problem + std::cout << "Computing closest vertices" << std::endl; + double min_distance = std::numeric_limits::max(); + + std::size_t counter = 0; + std::cout << "Vertices: " << p.size_of_vertices() << std::endl; + for (typename Polyhedron::Vertex_const_iterator v1 = p.vertices_begin(); + v1 != p.vertices_end(); v1++) + { + if (counter % 1000 == 0) + std::cout << counter << std::endl; + + typename Polyhedron::Vertex_const_handle v2 = v1; + v2++; + std::size_t counter2 = 0; + for (;v2 != p.vertices_end(); v2++) + { + min_distance = std::min(min_distance, + CGAL::to_double(CGAL::squared_distance(v1->point(), v2->point()))); + + if (min_distance == 0) + return 0.0; + + counter2++; + } + + counter++; + } + + std::cout << " Done computing closest" << std::endl; + return min_distance; + } + //----------------------------------------------------------------------------- + template + static std::size_t min_vertex_degree(const Polyhedron& p) + { + std::size_t min_degree = std::numeric_limits::max(); + for (typename Polyhedron::Vertex_const_iterator it = p.vertices_begin(); + it != p.vertices_end(); it++) + { + if (!vertex_is_border(it)) + { + min_degree = std::min(min_degree, it->vertex_degree()); + } + } + return min_degree; + } + //----------------------------------------------------------------------------- + template + static void remove_vertex(Polyhedron& P, typename Polyhedron::Vertex_handle v) + { + typedef typename Polyhedron::Halfedge_around_vertex_circulator Vertex_circulator; + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + + // std::cout << "Removing vertex" << std::endl; + + Vertex_circulator h = v->vertex_begin(); + Vertex_circulator start = h; + + std::vector to_be_removed; + + do + { + if (!h->is_border()) + to_be_removed.push_back(h); + + h++; + } while (h != start); + + + // std::cout << "Removing " << to_be_removed.size() << " halfedges" << std::endl; + for (auto it = to_be_removed.begin(); it != to_be_removed.end(); it++) + { + P.erase_facet(*it); + } + + // std::cout << " done removing vertex" << std::endl; + } + //----------------------------------------------------------------------------- + template + static std::size_t remove_self_intersections(Polyhedron& P) + { + std::size_t removed = 0; + + // typedef typename Polyhedron::Traits Polyhedron_traits; + typedef typename Polyhedron::Facet_handle Facet_handle; + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + std::vector > intersections; + CGAL::Polygon_mesh_processing::self_intersections(P, std::back_inserter(intersections)); + + while (intersections.size() > 0) + { + std::cout << "Removing self intersection (" << intersections.size() << ")" << std::endl; + const typename Polyhedron::Facet_handle f1 = intersections.front().first; + const typename Polyhedron::Facet_handle f2 = intersections.front().second; + + std::deque queue1; + queue1.push_back(f1); + + std::deque queue2; + queue2.push_back(f2); + + std::set to_be_removed1; + to_be_removed1.insert(f1); + + std::set to_be_removed2; + to_be_removed2.insert(f2); + + bool f1_done = false; + + while (!f1_done) + { + // Pop from queue 1 + if (queue1.size() > 0) + { + to_be_removed1.insert(queue1.front()); + Halfedge_handle start = queue1.front()->halfedge(); + + queue1.pop_front(); + Halfedge_handle current = start; + + do + { + std::cout << "Spreading out 1" << std::endl; + if (!current->is_border_edge()) + { + //if (to_be_removed2.count(current->opposite()->facet()) > 0) + if (current->opposite()->facet() == f2) + { + f1_done = true; + break; + } + else + { + queue1.push_back(current->opposite()->facet()); + } + } + current = current->next(); + } while (current != start); + } + else + { + f1_done = true; + } + } + + bool f2_done = false; + + while (!f2_done) + { + // Pop from queue 2 + if (queue2.size() > 0) + { + to_be_removed2.insert(queue2.front()); + Halfedge_handle start = queue2.front()->halfedge(); + queue2.pop_front(); + Halfedge_handle current = start; + + do + { + std::cout << "Spreading out 2" << std::endl; + if (!current->is_border_edge()) + { + // if (to_be_removed1.count(current->opposite()->facet()) > 0) + if (current->opposite()->facet() == f1) + { + f2_done = true; + break; + } + else + { + queue2.push_back(current->opposite()->facet()); + } + } + current = current->next(); + } while (current != start); + } + else + { + f2_done = true; + } + } + + std::cout << "To be removed 1: " << to_be_removed1.size() << std::endl; + std::cout << "To be removed 2: " << to_be_removed2.size() << std::endl; + + to_be_removed1.insert(to_be_removed2.begin(), + to_be_removed2.end()); + + for (typename Polyhedron::Face_handle f : to_be_removed1) + { + P.erase_facet(f->halfedge()); + dolfin_assert(P.is_valid()); + removed++; + } + + + /* for (typename Polyhedron::Face_handle f : to_be_removed2) */ + /* { */ + /* P.erase_facet(f->halfedge()); */ + /* dolfin_assert(P.is_valid()); */ + /* removed++; */ + /* } */ + + dolfin_assert(P.is_valid()); + + intersections.clear(); + CGAL::Polygon_mesh_processing::self_intersections(P, std::back_inserter(intersections)); + + break; + } + + return removed; + } + //----------------------------------------------------------------------------- + template + static void filter_sharp_features(Polyhedron& P, int start_facet, double tolerance) + { + typedef typename Polyhedron::Facet_iterator Facet_iterator; + typedef typename Polyhedron::Facet_handle Facet_handle; + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + typedef typename Polyhedron::Traits::Triangle_3 Triangle_3; + + /* { */ + /* std::ofstream outfile("before_filtering.off"); */ + /* outfile << P; */ + /* } */ + + /* std::cout << "Filter sharp features" << std::endl; */ + + const double cos_tolerance = std::cos(tolerance); + // std::cout << "tolerance: " << cos_tolerance << std::endl; + Facet_iterator fit = P.facets_begin(); + for (int i = 0; i < start_facet; i++) + fit++; + + std::deque queue; + { + Halfedge_handle h = fit->halfedge(); + // std::cout << "Starting facet: Triangle " << h->vertex()->point() << ", "; + h = h->next(); + // std::cout << h->vertex()->point() << ", "; + h = h->next(); + // std::cout << h->vertex()->point() << std::endl; + } + + std::set visited; + std::set to_be_removed; + for (Facet_iterator fit = P.facets_begin(); fit != P.facets_end(); fit++) + to_be_removed.insert(fit); + + // std::cout << "Number of facets: " << P.size_of_facets() << std::endl; + + queue.push_back(fit); + while (!queue.empty()) + { + // std::cout << "In queue" << std::endl; + Facet_handle f = queue.front(); + queue.pop_front(); + + if (visited.count(f) > 0) + { + // std::cout << "Already handled" << std::endl; + continue; + } + + visited.insert(f); + to_be_removed.erase(f); + + const Halfedge_handle start = f->halfedge(); + Halfedge_handle current = start; + do + { + // std::cout << "Exploring neighbor" << std::endl; + if (!current->opposite()->is_border()) + { + + Triangle_3 t1 = get_facet_triangle(current); + Triangle_3 t2 = get_facet_triangle(current->opposite()); + if (get_triangle_cos_angle(t1, t2) > cos_tolerance) + { + queue.push_back(current->opposite()->facet()); + } + } + + current = current->next(); + } while (current != start); + } + + // std::cout << "Remove " << to_be_removed.size() << " facets" << std::endl; + + for (auto fit = to_be_removed.begin(); fit != to_be_removed.end(); fit++) + { + P.erase_facet( (*fit)->halfedge() ); + } + + /* { */ + /* std::ofstream outfile("after_filtering.off"); */ + /* outfile << P; */ + /* } */ + } + +//----------------------------------------------------------------------------- + template + static std::pair + get_aabb(const Polyhedron& P) + { + typedef typename Polyhedron::Vertex_const_iterator Vertex_const_iterator; + typedef typename Polyhedron::Traits::Point_3 Point_3; + typedef typename Polyhedron::Traits::FT FT; + Vertex_const_iterator it = P.vertices_begin(); + + FT x_min = it->point().x(); + FT y_min = it->point().y();; + FT z_min = it->point().z(); + FT x_max = x_min; + FT y_max = y_min; + FT z_max = z_min; + it++; + + for (;it != P.vertices_end(); it++) + { + const Point_3& current = it->point(); + x_min = std::min(x_min, current.x()); + y_min = std::min(y_min, current.y()); + z_min = std::min(z_min, current.z()); + + x_max = std::max(x_max, current.x()); + y_max = std::max(y_max, current.y()); + z_max = std::max(z_max, current.z()); + } + + return std::make_pair(Point_3(x_min, y_min, z_min), + Point_3(x_max, y_max, z_max)); + } + //----------------------------------------------------------------------------- + template + static double sharpest_edge(const Polyhedron& P) + { + typedef typename Polyhedron::Traits Kernel; + typedef typename Polyhedron::Edge_const_iterator Edge_const_iterator; + typedef typename Kernel::Vector_3 Vector_3; + + double min_cos = 1.0; + for (Edge_const_iterator it = P.edges_begin(); it != P.edges_end(); it++) + { + //const Point_3 a = it->vertex()->point(); + const Vector_3 n1 = CGAL::normal(it->vertex()->point(), + it->next()->vertex()->point(), + it->next()->next()->vertex()->point()); + const Vector_3 n2 = CGAL::normal(it->opposite()->vertex()->point(), + it->opposite()->next()->vertex()->point(), + it->opposite()->next()->next()->vertex()->point()); + min_cos = std::min(min_cos, + CGAL::to_double(n1*n2)/sqrt(CGAL::to_double(n1.squared_length()*n2.squared_length()))); + } + + return acos(min_cos); + } + //----------------------------------------------------------------------------- + template + static void center_vertex_refinement(Polyhedron& P) + { + // Precollect facet handles since we are adding facets within the loop + std::vector facets; + facets.reserve(P.size_of_facets()); + for (typename Polyhedron::Facet_iterator fit = P.facets_begin(); + fit != P.facets_end(); + fit++) + { + facets.push_back(fit); + } + + for (typename Polyhedron::Facet_handle f : facets) + { + typename Polyhedron::Halfedge_handle h = f->halfedge(); + typename Polyhedron::Traits::Point_3 c = CGAL::centroid(h->vertex()->point(), + h->next()->vertex()->point(), + h->next()->next()->vertex()->point()); + c.exact(); + typename Polyhedron::Halfedge_handle h_new = P.create_center_vertex(h); + h_new->vertex()->point() = c; + } + } + //----------------------------------------------------------------------------- + template + static void split_edge_refinement(Polyhedron& P) + { + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + typedef typename Polyhedron::Traits::Point_3 Point_3; + + // Collect vertices from the original + std::set facets; + // facets.reserve(P.size_of_facets()); + for (typename Polyhedron::Facet_iterator fit = P.facets_begin(); + fit != P.facets_end(); + fit++) + { + const Halfedge_handle h = fit->halfedge(); + facets.insert(h); + } + + // Split all edges + { + std::vector edges; + for (typename Polyhedron::Edge_iterator eit = P.edges_begin(); + eit != P.edges_end(); eit++) + { + edges.push_back(eit); + } + + for (Halfedge_handle h : edges) + { + const Point_3 p = CGAL::midpoint(h->vertex()->point(), + h->opposite()->vertex()->point()); + Halfedge_handle opposite = h->opposite(); + Halfedge_handle h_new = P.split_edge(h); + h_new->vertex()->point() = p; + + // This is suboptimal, but split_edge(h) changes h->opposite() to point + // to the newly inserted vertex, so we have to check and update if + // h->opposite() is stored in facets + if (facets.erase(opposite)) + { + facets.insert(h_new->opposite()); + } + } + } + + for (Halfedge_handle h : facets) + { + Halfedge_handle h2 = h->next()->next(); + Halfedge_handle h3 = h2->next()->next(); + P.split_facet(h->next(), h->prev()); + P.split_facet(h2->next(), h2->prev()); + P.split_facet(h3->next(), h3->prev()); + } + } +}; + //----------------------------------------------------------------------------- +// Taken from demo/Polyhedron/Scene_nef_polyhedron_item.cpp in the +// CGAL source tree. +// Quick hacks to convert polyhedra from exact to inexact and +// vice-versa +template +struct Copy_polyhedron_to + : public CGAL::Modifier_base +{ + Copy_polyhedron_to(const Polyhedron_input& in_poly) + : _in_poly(in_poly) {} + + void operator()(typename Polyhedron_output::HalfedgeDS& out_hds) + { + typedef typename Polyhedron_output::HalfedgeDS Output_HDS; + //typedef typename Polyhedron_input::HalfedgeDS Input_HDS; + + CGAL::Polyhedron_incremental_builder_3 builder(out_hds); + + typedef typename Polyhedron_input::Vertex_const_iterator Vertex_const_iterator; + typedef typename Polyhedron_input::Facet_const_iterator Facet_const_iterator; + typedef typename Polyhedron_input::Halfedge_around_facet_const_circulator HFCC; + + builder.begin_surface(_in_poly.size_of_vertices(), + _in_poly.size_of_facets(), + _in_poly.size_of_halfedges()); + + for(Vertex_const_iterator + vi = _in_poly.vertices_begin(), end = _in_poly.vertices_end(); + vi != end ; ++vi) + { + typename Polyhedron_output::Point_3 p(::CGAL::to_double( vi->point().x()), + ::CGAL::to_double( vi->point().y()), + ::CGAL::to_double( vi->point().z())); + builder.add_vertex(p); + } + + typedef CGAL::Inverse_index Index; + Index index(_in_poly.vertices_begin(), _in_poly.vertices_end()); + + for(Facet_const_iterator + fi = _in_poly.facets_begin(), end = _in_poly.facets_end(); + fi != end; ++fi) + { + HFCC hc = fi->facet_begin(); + HFCC hc_end = hc; + // std::size_t n = circulator_size( hc); + // CGAL_assertion( n >= 3); + builder.begin_facet (); + do + { + builder.add_vertex_to_facet(index[hc->vertex()]); + ++hc; + } while( hc != hc_end); + builder.end_facet(); + } + builder.end_surface(); + } // end operator()(..) +private: + const Polyhedron_input& _in_poly; +}; // end Copy_polyhedron_to<> + +template +void copy_to(const Poly_A& poly_a, Poly_B& poly_b) +{ + Copy_polyhedron_to modifier(poly_a); + poly_b.delegate(modifier); + // CGAL_assertion(poly_b.is_valid()); +} +} + +#endif diff --git a/src/STLFileReader.cpp b/src/STLFileReader.cpp new file mode 100644 index 0000000..3e124da --- /dev/null +++ b/src/STLFileReader.cpp @@ -0,0 +1,335 @@ +// Copyright (C) 2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#include +#include "FuzzyPointLocator.h" + +#include +#include +#include +#include + +#define BOOST_FILESYSTEM_NO_DEPRECATED +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +namespace +{ +inline double strToDouble(const std::string& s) +{ + std::istringstream is(s); + double val; + is >> val; + + return val; +} + +// get next line of file and trim away whitespace +inline void get_next_line(std::ifstream& file, std::string& line, std::size_t &lineno) +{ + // Skip white + do + { + std::getline(file, line); + boost::algorithm::trim(line); + lineno++; + } while (file.good() && line.size() == 0); +} + +} // end anonymous namespace +//----------------------------------------------------------------------------- +namespace mshr +{ + +void STLFileReader::read(const std::string filename, + std::vector >& vertices, + std::vector >& facets) +{ + + dolfin::log(dolfin:: TRACE, "Reading surface from %s ", filename.c_str()); + + // vertices.clear(); + facets.clear(); + + typedef boost::tokenizer > tokenizer; + + std::ifstream file(filename.c_str()); + if (!file.is_open()) + { + dolfin::dolfin_error("STLFileReader.cpp", + "open .stl file to read 3D surface", + "Failed to open file"); + } + + FuzzyPointMap vertex_map(1e-10); + + std::string line; + std::size_t lineno = 0; + const boost::char_separator sep(" "); + + // Read the first line and trim away whitespaces + get_next_line(file, line, lineno); + + if (line.substr(0, 5) != "solid") + { + dolfin::dolfin_error("STLFileReader.cpp", + "open .stl file to read 3D surface", + "File does not start with \"solid\" (line %u", lineno); + } + + // TODO: Read name of solid + + do + { + // Some files contain color information before the vertex information + get_next_line(file, line, lineno); + } while (line.substr(0, 5) != "facet"); + + while (file.good()) + { + bool has_normal = false; + dolfin::Point normal; + + // Read the line "facet normal n1 n2 n3" + { + tokenizer tokens(line, sep); + tokenizer::iterator tok_iter = tokens.begin(); + + if (*tok_iter != "facet") + dolfin::dolfin_error("STLFileReader.cpp", + "open .stl file to read 3D surface", + "Expected keyword \"facet\" (line %u)", lineno); + ++tok_iter; + + // Check if a normal different from zero is given + if (tok_iter != tokens.end()) + { + if (*tok_iter != "normal") + dolfin::dolfin_error("STLFileReader.cpp", + "open .stl file to read 3D surface", + "Expected keyword \"normal\"(line %u)", lineno); + ++tok_iter; + + //dolfin::cout << "Read line: " << line << dolfin::endl; + + for (std::size_t i = 0; i < 3; ++i) + { + normal[i] = strToDouble(*tok_iter); + ++tok_iter; + } + + if (normal.norm() > DOLFIN_EPS) + has_normal = true; + + if (tok_iter != tokens.end()) + dolfin::dolfin_error("STLFileReader.cpp", + "open .stl file to read 3D surface", + "Expected end of line (line %u)", lineno); + } + } + + // if (has_normal) + // { + // dolfin::cout << "Has normal" << dolfin::endl; + // dolfin::cout << normal << dolfin::endl; + // } + + // Read "outer loop" line + get_next_line(file, line, lineno); + + if (line != "outer loop") + dolfin::dolfin_error("STLFileReader.cpp", + "open .stl file to read 3D surface", + "Expected keyword 'outer loop' (line %u)", lineno); + + std::array v_indices; + + get_next_line(file, line, lineno); + + tokenizer tokens(line, sep); + tokenizer::iterator tok_iter = tokens.begin(); + + if (*tok_iter != "vertex") + { + dolfin::dolfin_error("STLFileReader.cpp", + "open .stl file to read 3D surface", + "Expected keyword vertex (line %u)", lineno); + } + + int counter = 0; + + // Read lines with vertices + do + { + // Only support for triangulated surfaces for now + dolfin_assert(counter < 3); + + // Advance to next + ++tok_iter; + + const double x = strToDouble(*tok_iter); ++tok_iter; + const double y = strToDouble(*tok_iter); ++tok_iter; + const double z = strToDouble(*tok_iter); ++tok_iter; + + const std::array vertex = {{x, y, z}}; + + // Insert point and get index. vertex_map takes care of merging close + // vertices + v_indices[counter] = vertex_map.insert_point(vertex); + + // Get next line + get_next_line(file, line, lineno); + + tokens = tokenizer(line, sep); + tok_iter = tokens.begin(); + + counter++; + } while (*tok_iter == "vertex"); + + // Read 'endloop' line + if (line != "endloop") + { + dolfin::dolfin_error("STLFileReader.cpp", + "open .stl file to read 3D surface", + "Expected keyword endloop (line %u)", lineno); + } + + get_next_line(file, line, lineno); + if (line != "endfacet") + { + dolfin::dolfin_error("STLFileReader.cpp", + "open .stl file to read 3D surface", + "Expected keyword endfacet (line %u)", lineno); + } + + // Add facet to output + facets.push_back(v_indices); + + // Get orientation right if normal is given + if (has_normal) + { + // Compute normal + const dolfin::Point v1(3, vertex_map[v_indices[0]].data()); + const dolfin::Point v2(3, vertex_map[v_indices[1]].data()); + const dolfin::Point v3(3, vertex_map[v_indices[2]].data()); + + const dolfin::Point a = v2-v1; + const dolfin::Point b = v3-v1; + + dolfin::Point n = a.cross(b); + n /= n.norm(); + + // dolfin::cout << "Normal: " << n << dolfin::endl; + // if ( (n - normal).norm() > 1e-5 ) + // dolfin::cout << "Diff: " << (n - normal).norm() << dolfin::endl; + } + + // Get next line + // either start of next facet or endsolid + get_next_line(file, line, lineno); + + if (line.substr(0, 5) != "facet") + break; + } + + // Read the 'endsolid' line + tokenizer tokens(line, sep); + tokenizer::iterator tok_iter = tokens.begin(); + + if (*tok_iter != "endsolid") + { + dolfin::dolfin_error("STLFileReader.cpp", + "open .stl file to read 3D surface", + "Expected keyword endsolid at line %u", lineno); + } + ++tok_iter; + + vertices = vertex_map.get_points(); + + // TODO: Check name of solid + dolfin::log(dolfin::TRACE, "Done reading surface"); + + // closest_vertices(vertex_map); +} +void STLFileReader::write(const std::string filename, + std::vector >& vertices, + std::vector >& facets) +{ + + std::ofstream file(filename); + file.precision(6); + + if (!file.is_open()) + { + dolfin::dolfin_error("STLFileReader.cpp", + "open file to write stl data", + "Failed to open file"); + } + + file << "solid "<< filename << std::endl; + + + + + for (const std::array& f : facets) + { + + std::array n; + float vec1x,vec1y,vec1z; + float vec2x,vec2y,vec2z; + float size; + + std::array& v0 = vertices[f[0]]; + std::array& v1 = vertices[f[1]]; + std::array& v2 = vertices[f[2]]; + + vec1x = v1[0] - v0[0]; + vec1y = v1[1] - v0[1]; + vec1z = v1[2] - v0[2]; + + vec2x = v2[0] - v0[0]; + vec2y = v2[1] - v0[1]; + vec2z = v2[2] - v0[2]; + + n[0] = vec1y*vec2z-vec1z*vec2y; + n[1] = vec1z*vec2x-vec1x*vec2z; + n[2] = vec1x*vec2y-vec1y*vec2x; + size = sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]); + + file << "facet normal " << n[0]/size << " " << n[1]/size << " " << n[2]/size <. + +// OBS! Experimental code + +#include +#include "FuzzyPointLocator.h" + +#include +#include +#include +#include +#include +#include + +// #include +// typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; +// typedef Kernel::Point_3 Point_3; +// typedef Kernel::Triangle_3 Triangle_3; + +// namespace +// { +// inline bool triangles_intersect(const std::vector >& vertices, +// const std::vector >& facets, +// std::size_t a, +// std::size_t b) +// { +// const std::array& ta = facets[a]; +// const std::array& tb = facets[b]; + +// std::cout << " Intersection test: " << std::endl; +// std::cout << " Facet " << a << ": (" << ta[0] << ", " << ta[1] << ", " << ta[2] << ")" << std::endl; +// std::cout << " Facet " << b << ": (" << tb[0] << ", " << tb[1] << ", " << tb[2] << ")" << std::endl; + +// for (std::size_t i = 0; i < 3; i++) +// { +// for (std::size_t j = 0; j < 3; j++) +// { +// if (ta[i] == tb[j] || (ta[i] == tb[(j+1)%3] && ta[(i+1)%3] == tb[j])) +// { +// std::cout << " Neighbor" << std::endl; +// return false; +// } +// } +// } + +// Triangle_3 t1(Point_3(vertices[ta[0]][0], vertices[ta[0]][1], vertices[ta[0]][2]), +// Point_3(vertices[ta[1]][0], vertices[ta[1]][1], vertices[ta[1]][2]), +// Point_3(vertices[ta[2]][0], vertices[ta[2]][1], vertices[ta[2]][2])); + +// Triangle_3 t2(Point_3(vertices[tb[0]][0], vertices[tb[0]][1], vertices[tb[0]][2]), +// Point_3(vertices[tb[1]][0], vertices[tb[1]][1], vertices[tb[1]][2]), +// Point_3(vertices[tb[2]][0], vertices[tb[2]][1], vertices[tb[2]][2])); + +// const bool i = CGAL::do_intersect(t1, t2); +// std::cout << " Result: " << (i ? "True" : "False") << std::endl; +// return i; +// } +// } + +namespace mshr +{ + +void SurfaceConsistency::checkConnectivity(std::vector >& facets, + std::set& duplicating, + bool error) +{ + log(dolfin::TRACE, "Checking connectivity"); + + // Store halfedges + std::map, std::size_t> halfedges; + + for (std::size_t facet_no = 0; facet_no < facets.size(); facet_no++) + { + std::array& f = facets[facet_no]; + // Check for (topologically) degenerate facets + if ( f[0] == f[1] || f[0] == f[2] || f[1] == f[2] ) + dolfin::dolfin_error("SurfaceConsistency.cpp", + "confirm surface connectivity", + "Facet %d is topologically degenerate", facet_no); + + if (halfedges.count(std::make_pair(f[0], f[1])) > 0 || + halfedges.count(std::make_pair(f[1], f[2])) > 0 || + halfedges.count(std::make_pair(f[2], f[0])) > 0) + { + duplicating.insert(facet_no); + } + else + { + halfedges[std::make_pair(f[0], f[1])] = facet_no; + halfedges[std::make_pair(f[1], f[2])] = facet_no; + halfedges[std::make_pair(f[2], f[0])] = facet_no; + } + } +} +//----------------------------------------------------------------------------- +void SurfaceConsistency::filterFacets(const std::vector >& facets, + const std::vector >& vertices, + std::size_t start, std::set& skip) +{ + // Map egdes (ordered pairs of vertices) to facets + std::map, std::size_t> edge_map; + + for (std::size_t i = 0; i < facets.size(); i++) + { + const std::array& facet = facets[i]; + std::size_t prev = facet[facet.size()-1]; + for (auto vit = facet.begin(); vit != facet.end(); vit++) + { + const std::pair e(prev, *vit); + edge_map[e] = i; + prev = *vit; + } + + skip.insert(i); + } + + // const std::size_t global_max = skip.size(); + + std::set visited; + //std::vector included; + std::deque queue; + if (skip.count(start) > 0) + queue.push_front(start); + // else + // std::cout << " Already added" << std::endl; + + std::size_t global_count = 0; + while (!queue.empty()) + { + // dolfin_assert(global_count <= global_max); + + std::size_t current = queue.front(); + queue.pop_front(); + + const std::array& current_facet = facets[current]; + // std::cout << "-- Processing " << current << ", vertices: " << current_facet[0] << ", " << current_facet[1] << ", " << current_facet[2] << std::endl; + + visited.insert(current); + + // don't skip this facet + if (skip.count(current) > 0) + { + // bool intersects = false; + // for (auto it = included.begin(); it != included.end(); it++) + // { + // if (triangles_intersect(vertices, facets, current, *it)) + // { + // intersects = true; + // break; + // } + // } + + // if (intersects) + // { + // std::cout << " SKIPPING" << std::endl; + // {int tmp; std::cin >> tmp;} + // continue; + // } + + skip.erase(current); + //included.push_back(current); + + std::size_t prev = current_facet[2]; + for (auto fit = current_facet.begin(); fit != current_facet.end(); fit++) + { + const std::pair opposite(*fit, prev); + // std::cout << " opposite: " << opposite.first << " " << opposite.second << std::endl; + if (edge_map.count(opposite) > 0) + { + std::size_t opposite_facet = edge_map[opposite]; + if (visited.count(opposite_facet) == 0) + { + queue.push_back(opposite_facet); + //std::cout << " pushing: " << edge_map[opposite] << std::endl; + } + } + prev = *fit; + } + + // std::cout << "Size of skip: " << skip.size() << ", size of queue: " << queue.size() << ", current: " << current << std::endl; + // {int tmp; std::cin >> tmp;} + global_count++; + } + } +} +//----------------------------------------------------------------------------- +std::pair > >, + std::unique_ptr > > > +SurfaceConsistency::merge_close_vertices(const std::vector >& facets, + const std::vector >& vertices, + double tolerance) +{ + FuzzyPointMap point_map(tolerance); + std::vector vertex_mapping; + vertex_mapping.reserve(vertices.size()); + + + for (std::size_t i = 0; i < vertices.size(); i++) + { + const std::array& v = vertices[i]; + const std::size_t new_index = point_map.insert_point(v); + vertex_mapping.push_back(new_index); + } + + // std::cout << "Distinct vertices: " << point_map.size() << std::endl; + + std::unique_ptr > > new_vertices(new std::vector >(point_map.get_points())); + std::unique_ptr > > new_facets(new std::vector >); + for (const std::array& t : facets) + { + new_facets->push_back({ {vertex_mapping[t[0]], vertex_mapping[t[1]], vertex_mapping[t[2]]} }); + } + + return std::make_pair(std::move(new_vertices), std::move(new_facets)); +} +//----------------------------------------------------------------------------- +void SurfaceConsistency::orient_component(std::vector >& facets, + std::size_t start) +{ + log(dolfin::TRACE, "Checking facet orientation"); + + // Map from edge (pair of vertices) to two triangles + std::map, std::pair > edge_map; + for (std::size_t i = 0; i < facets.size(); i++) + { + const std::array& f = facets[i]; + for (std::size_t j = 0; j < 3; j++) + { + auto edge = std::make_pair(std::min(f[j], f[(j+1)%3]), std::max(f[j], f[(j+1)%3])); + if (edge_map.count(edge) > 0) + edge_map[edge].second = i; + else + edge_map[edge] = std::make_pair(i, std::numeric_limits::max()); + } + } + + std::deque queue; + std::set visited; + queue.push_back(start); + + std::size_t counter = 0; + std::size_t flipped = 0; + while (!queue.empty()) + { + std::size_t current = queue.front(); + const std::array& current_facet = facets[current]; + queue.pop_front(); + + if (visited.count(current) == 0) + { + counter++; + visited.insert(current); + + for (std::size_t j = 0; j < 3; j++) + { + auto edge = std::make_pair(std::min(current_facet[j], current_facet[(j+1)%3]), std::max(current_facet[j], current_facet[(j+1)%3])); + dolfin_assert(edge_map.count(edge) > 0); + + auto facet_pair = edge_map[edge]; + const std::size_t opposite = facet_pair.first == current ? facet_pair.second : facet_pair.first; + + if (opposite != std::numeric_limits::max() && visited.count(opposite) == 0) + { + dolfin_assert(opposite != current); + + std::array& opposite_facet = facets[opposite]; + for (std::size_t k = 0; k < 3; k++) + { + const std::size_t a = opposite_facet[k]; + const std::size_t b = opposite_facet[(k+1)%3]; + if ( (a == current_facet[0] && b == current_facet[1]) || + (a == current_facet[1] && b == current_facet[2]) || + (a == current_facet[2] && b == current_facet[0])) + { + flipped++; + std::swap(opposite_facet[0], opposite_facet[1]); + break; + } + } + queue.push_back(opposite); + } + } + } + } + log(dolfin::TRACE, "Flipped %u triangles", flipped); +} + +std::size_t SurfaceConsistency::remove_null_facets(std::vector>& facets) +{ + std::size_t counter = 0; + + std::vector>::iterator it = facets.begin(); + + while (it != facets.end()) + { + const std::array& f = *it; + if (f[0] == f[1] || f[0] == f[2] || f[1] == f[2]) + { + *it = facets.back(); + facets.pop_back(); + counter++; + } + else + it++; + } + + return counter; +} + +std::size_t SurfaceConsistency::remove_isolated_vertices(std::vector>& vertices, + std::vector>& facets) +{ + std::size_t counter = 0; + std::vector is_connected(vertices.size()); + std::fill(is_connected.begin(), is_connected.end(), false); + for (const std::array& f : facets) + { + is_connected[f[0]] = true; + is_connected[f[1]] = true; + is_connected[f[2]] = true; + } + + for (std::size_t i_ = 0; i_ < is_connected.size(); i_++) + { + // Need to do this in reverse order, so the vertices with higher index is + // processed first to avoid invalidating the + const std::size_t i = is_connected.size()-i_-1; + + if (!is_connected[i]) + { + vertices.erase(vertices.begin()+i); + + for (std::array& f : facets) + { + if (f[0] > i) f[0]--; + if (f[1] > i) f[1]--; + if (f[2] > i) f[2]--; + } + + counter++; + } + } + + return counter; +} + + +} diff --git a/src/SurfaceReconstruction.cpp b/src/SurfaceReconstruction.cpp new file mode 100644 index 0000000..c956895 --- /dev/null +++ b/src/SurfaceReconstruction.cpp @@ -0,0 +1,370 @@ +// Copyright (C) 2015-2016 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#include +#include +#include + +#define CGAL_EIGEN3_ENABLED true +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// Based on CGAL/Surface_reconstruction_points_3/poisson_reconstruction_example.cpp +typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; +typedef Kernel::FT FT; +typedef Kernel::Point_3 Point; +typedef Kernel::Vector_3 Vector; +typedef CGAL::Point_with_normal_3 Point_with_normal; +typedef Kernel::Sphere_3 Sphere; +typedef std::vector PointList; +typedef CGAL::Poisson_reconstruction_function Poisson_reconstruction_function; +typedef CGAL::Surface_mesh_default_triangulation_3 STr; +typedef CGAL::Surface_mesh_complex_2_in_triangulation_3 C2t3; +typedef CGAL::Implicit_surface_3 Surface_3; +typedef typename C2t3::Triangulation Tr; +typedef typename Tr::Vertex_handle Vertex_handle; +typedef typename Tr::Edge Edge; +typedef typename Tr::Facet Facet; +typedef typename Tr::Finite_facets_iterator Finite_facets_iterator; + + +namespace +{ + +template +std::size_t get_vertex_index(std::vector>& vertices, + Vertex_handle vh, + std::map& V, + std::size_t& inum) +{ + typedef typename std::map::iterator map_iterator; + std::pair insert_res = V.insert( std::make_pair(vh,inum) ); + if ( insert_res.second ) + { + typename Tr::Point p = static_cast(vh->point()); + vertices.push_back({ {CGAL::to_double(p[0]), + CGAL::to_double(p[1]), + CGAL::to_double(p[2])} }); + ++inum; + } + return insert_res.first->second; +} +//----------------------------------------------------------------------------- +void export_triangulation(const C2t3& c2t3, + std::vector>& vertices, + std::vector>& facets) +{ + const Tr& tr = c2t3.triangulation(); + const typename Tr::size_type number_of_facets = c2t3.number_of_facets(); + + vertices.reserve(tr.number_of_vertices()); + facets.reserve(number_of_facets); + + // Finite vertices coordinates. + Finite_facets_iterator fit = tr.finite_facets_begin(); + std::set oriented_set; + std::stack stack; + + //CGAL_assertion_code(typename Tr::size_type nb_facets = 0; ) + + while (oriented_set.size() != number_of_facets) + { + while ( fit->first->is_facet_on_surface(fit->second) == false || + oriented_set.find(*fit) != oriented_set.end() || + oriented_set.find(c2t3.opposite_facet(*fit)) != oriented_set.end() ) + { + ++fit; + } + oriented_set.insert(*fit); + stack.push(*fit); + while(! stack.empty() ) + { + Facet f = stack.top(); + stack.pop(); + for(int ih = 0 ; ih < 3 ; ++ih) + { + const int i1 = tr.vertex_triple_index(f.second, tr. cw(ih)); + const int i2 = tr.vertex_triple_index(f.second, tr.ccw(ih)); + if( c2t3.face_status(Edge(f.first, i1, i2)) == C2t3::REGULAR ) + { + Facet fn = c2t3.neighbor(f, ih); + if (oriented_set.find(fn) == oriented_set.end() && + oriented_set.find(c2t3.opposite_facet(fn)) == oriented_set.end()) + { + oriented_set.insert(fn); + stack.push(fn); + } + } // end "if the edge is regular" + } // end "for each neighbor of f" + } // end "stack non empty" + } // end "oriented_set not full" + + // Orients the whole mesh towards outside: + // - find the facet with max z + typename std::set::const_iterator top_facet = oriented_set.begin(); + for(typename std::set::const_iterator fit = oriented_set.begin(); + fit != oriented_set.end(); + ++fit) + { + double top_z = + (top_facet->first->vertex(tr.vertex_triple_index(top_facet->second, 0))->point().z() + + top_facet->first->vertex(tr.vertex_triple_index(top_facet->second, 1))->point().z() + + top_facet->first->vertex(tr.vertex_triple_index(top_facet->second, 2))->point().z())/3.; + double z = + (fit->first->vertex(tr.vertex_triple_index(fit->second, 0))->point().z() + + fit->first->vertex(tr.vertex_triple_index(fit->second, 1))->point().z() + + fit->first->vertex(tr.vertex_triple_index(fit->second, 2))->point().z())/3.; + if (top_z < z) + top_facet = fit; + } + + // - orient the facet with max z towards +Z axis + Vertex_handle v0 = top_facet->first->vertex(tr.vertex_triple_index(top_facet->second, 0)); + Vertex_handle v1 = top_facet->first->vertex(tr.vertex_triple_index(top_facet->second, 1)); + Vertex_handle v2 = top_facet->first->vertex(tr.vertex_triple_index(top_facet->second, 2)); + Vector normal = cross_product(v1->point()-v0->point(), v2->point()-v1->point()); + const Vector Z(0, 0, 1); + bool regular_orientation = (Z * normal >= 0); + + // used to set indices of vertices + std::map V; + std::size_t inum = 0; + + for(typename std::set::const_iterator fit = + oriented_set.begin(); + fit != oriented_set.end(); + ++fit) + { + std::size_t indices[3]; + std::size_t index = 0; + for (std::size_t i = 0; i<3; i++) + { + indices[index++] = get_vertex_index(vertices, + fit->first->vertex(tr.vertex_triple_index(fit->second, i)), + V, + inum); + } + + facets.push_back({ {indices[0], + regular_orientation ? indices[1] : indices[2], + regular_orientation ? indices[2] : indices[1]} }); + } +} +} + +namespace mshr +{ +//----------------------------------------------------------------------------- +void mshr::SurfaceReconstruction::reconstruct(const std::vector& vertices, + const std::vector& facets, + std::vector>& reconstructed_vertices, + std::vector>& reconstructed_facets, + double expansion) +{ + // Poisson options + FT sm_angle = 20.0; // Min triangle angle in degrees. + FT sm_radius = 10; // Max triangle size w.r.t. point set average spacing. + FT sm_distance = 0.10; // Surface Approximation error w.r.t. point set average spacing. + + // Reads the point set file in points[]. + // Note: read_xyz_points_and_normals() requires an iterator over points + // + property maps to access each point's position and normal. + // The position property map can be omitted here as we use iterators over Point_3 elements. + PointList points; + for (std::size_t i = 0; i < facets.size(); i += 3) + { + const Point a(vertices[facets[i]*3], vertices[facets[i]*3+1], vertices[facets[i]*3+2]); + const Point b(vertices[facets[i+1]*3], vertices[facets[i+1]*3+1], vertices[facets[i+1]*3+2]); + const Point c(vertices[facets[i+2]*3], vertices[facets[i+2]*3+1], vertices[facets[i+2]*3+2]); + + // compute normal + const Vector normal = CGAL::cross_product(b-a, c-a); + const Point centroid = CGAL::ORIGIN + ((a-CGAL::ORIGIN)+(b-CGAL::ORIGIN)+(c-CGAL::ORIGIN))/3.0; + const Vector normal_normalized = normal/std::sqrt(normal.squared_length()); + Point_with_normal pm( centroid + normal_normalized*expansion, + normal_normalized ); + + points.push_back(pm); + } + + // Creates implicit function from the read points using the default solver. + // Note: this method requires an iterator over points + // + property maps to access each point's position and normal. + // The position property map can be omitted here as we use iterators over Point_3 elements. + log(dolfin::TRACE, "Construct implicit function"); + Poisson_reconstruction_function function(points.begin(), points.end(), + CGAL::make_normal_of_point_with_normal_map(PointList::value_type()) ); + log(dolfin::TRACE, "Compute Poisson indicator function"); + // Computes the Poisson indicator function f() + // at each vertex of the triangulation. + if ( ! function.compute_implicit_function() ) + { + dolfin::dolfin_error("SurfaceReconstruction.cpp", + "reconstruct surface", + "couldn't compute implicit function"); + } + // Computes average spacing + log(dolfin::TRACE, "Compute average spacing"); + FT average_spacing = CGAL::compute_average_spacing(points, + 6 /* knn = 1 ring */); + // Gets one point inside the implicit surface + // and computes implicit function bounding sphere radius. + Point inner_point = function.get_inner_point(); + Sphere bsphere = function.bounding_sphere(); + FT radius = std::sqrt(bsphere.squared_radius()); + // Defines the implicit surface: requires defining a + // conservative bounding sphere centered at inner point. + FT sm_sphere_radius = 5.0 * radius; + FT sm_dichotomy_error = sm_distance*average_spacing/1000.0; // Dichotomy error must be << sm_distance + Surface_3 surface(function, + Sphere(inner_point,sm_sphere_radius*sm_sphere_radius), + sm_dichotomy_error/sm_sphere_radius); + + // Defines surface mesh generation criteria + CGAL::Surface_mesh_default_criteria_3 criteria(sm_angle, // Min triangle angle (degrees) + sm_radius*average_spacing, // Max triangle size + sm_distance*average_spacing); // Approximation error + // Generates surface mesh with manifold option + log(dolfin::TRACE, "Invoke surface mesher"); + STr tr; // 3D Delaunay triangulation for surface mesh generation + C2t3 c2t3(tr); // 2D complex in 3D Delaunay triangulation + CGAL::make_surface_mesh(c2t3, // reconstructed mesh + surface, // implicit surface + criteria, // meshing criteria + CGAL::Manifold_with_boundary_tag()); // require manifold mesh + if(tr.number_of_vertices() == 0) + dolfin::dolfin_error("SurfaceReconstruction.cpp", + "reconstruct surface from point set", + "Couldn't reconstruct surface"); + + export_triangulation(c2t3, + reconstructed_vertices, + reconstructed_facets); +} + //----------------------------------------------------------------------------- +void SurfaceReconstruction::remesh(double edge_length, + double sharp_edge_tolerance, + const std::vector& vertices, + const std::vector& facets, + std::vector>& remeshed_vertices, + std::vector>& remeshed_facets) +{ + typedef CGAL::Surface_mesh Surface_mesh; + typedef Surface_mesh::Vertex_index Vertex_index; + typedef Surface_mesh::Face_index Face_index; + typedef Surface_mesh::Edge_index Edge_index; + typedef Surface_mesh::Halfedge_index Halfedge_index; + typedef boost::graph_traits::face_descriptor face_descriptor; + typedef boost::graph_traits::edge_descriptor edge_descriptor; + + namespace Parameters = CGAL::Polygon_mesh_processing::parameters; + + Surface_mesh m; + std::vector v_index_mapping; + v_index_mapping.reserve(vertices.size()); + for (std::size_t i = 0; i < vertices.size(); i += 3) + { + v_index_mapping.push_back(m.add_vertex(Point(vertices[i], vertices[i+1], vertices[i+2]))); + } + + for (std::size_t i = 0; i < facets.size(); i += 3) + { + m.add_face(v_index_mapping[facets[i]], + v_index_mapping[facets[i+1]], + v_index_mapping[facets[i+2]]); + } + + // Collect edges that should be protected during remeshing + const double cos_tolerance = cos(2*DOLFIN_PI*sharp_edge_tolerance/360); + + Surface_mesh::Property_map edge_constrained_map = + m.add_property_map("e:is_constrained", false).first; + + std::size_t num_protected = 0; + if (sharp_edge_tolerance > 0) + { + Surface_mesh::Property_map fnormals = + m.add_property_map("f:normals", CGAL::NULL_VECTOR).first; + + CGAL::Polygon_mesh_processing::compute_face_normals(m, fnormals); + + for (Edge_index e : m.edges()) + { + Halfedge_index h = e.halfedge(); + Face_index f1 = m.face(h); + Face_index f2 = m.face(m.opposite(h)); + const Vector& n1 = fnormals[f1]; + const Vector& n2 = fnormals[f2]; + + if (n1*n2 < cos_tolerance) + { + edge_constrained_map[e] = true; + num_protected++; + } + } + + m.remove_property_map(fnormals); + + dolfin::log(dolfin::TRACE, + "Number of protected edges: %u", num_protected); + } + + // Remeshing + CGAL::Polygon_mesh_processing::isotropic_remeshing(faces(m), + edge_length, + m, + Parameters::edge_is_constrained_map(edge_constrained_map) + ); + + remeshed_vertices.clear(); + remeshed_facets.clear(); + + std::vector vertex_index_mapping; + vertex_index_mapping.resize(m.num_vertices()); + int i = 0; + for(Vertex_index vd : m.vertices()) + { + const Point& p = m.point(vd); + remeshed_vertices.push_back({{CGAL::to_double(p[0]), CGAL::to_double(p[1]), CGAL::to_double(p[2])}}); + vertex_index_mapping[vd] = i; + i++; + } + + for (Face_index f : m.faces()) + { + const Halfedge_index h = m.halfedge(f); + const Halfedge_index h_next = m.next(h); + const Halfedge_index h_next_next = m.next(h_next); + + remeshed_facets.push_back({{vertex_index_mapping[m.source(h)], + vertex_index_mapping[m.source(h_next)], + vertex_index_mapping[m.source(h_next_next)]}}); + } +} + +} // end namespace mshr diff --git a/src/TetgenMeshGenerator3D.cpp b/src/TetgenMeshGenerator3D.cpp new file mode 100644 index 0000000..a21625d --- /dev/null +++ b/src/TetgenMeshGenerator3D.cpp @@ -0,0 +1,248 @@ +// Copyright (C) 2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#include + +#include +#include +#include +#include +#include + +#include + +// Bounding sphere computation +#include +#include +#include + +#include +#include + +namespace +{ +//----------------------------------------------------------------------------- +void build_dolfin_mesh(const tetgenio& tetgenmesh, dolfin::Mesh& dolfinmesh) +{ + // Clear mesh + // dolfin::Mesh::clear has been removed + // dolfinmesh.clear(); + + // Create and initialize mesh editor + dolfin::MeshEditor mesh_editor; + mesh_editor.open(dolfinmesh, dolfin::CellType::Type::tetrahedron, 3, 3); + mesh_editor.init_vertices(tetgenmesh.numberofpoints); + + const int offset = tetgenmesh.firstnumber; + + for (int i = 0; i < tetgenmesh.numberofpoints; i++) + { + + dolfin::Point p(tetgenmesh.pointlist[i * 3], + tetgenmesh.pointlist[i * 3 + 1], + tetgenmesh.pointlist[i * 3 + 2]); + mesh_editor.add_vertex(i, p); + } + + + mesh_editor.init_cells(tetgenmesh.numberoftetrahedra); + dolfin_assert(tetgenmesh.numberofcorners == 4); + + for (int i = 0; i < tetgenmesh.numberoftetrahedra; i++) + { + mesh_editor.add_cell(i, + tetgenmesh.tetrahedronlist[i*4 + 0]-offset, + tetgenmesh.tetrahedronlist[i*4 + 1]-offset, + tetgenmesh.tetrahedronlist[i*4 + 2]-offset, + tetgenmesh.tetrahedronlist[i*4 + 3]-offset); + } + + // Close mesh editor + mesh_editor.close(); +} +//----------------------------------------------------------------------------- +double bounding_sphere_radius(const std::vector& vertices) +{ + typedef double FT; + typedef CGAL::Cartesian K; + typedef CGAL::Min_sphere_of_spheres_d_traits_3 MinSphereTraits; + typedef CGAL::Min_sphere_of_spheres_d Min_sphere; + typedef MinSphereTraits::Sphere Sphere; + + std::vector S; + + for (std::size_t i = 0; i < vertices.size(); i += 3) + { + S.push_back(Sphere(K::Point_3( vertices[i], + vertices[i+1], + vertices[i+2]), 0.0)); + } + + Min_sphere ms(S.begin(), S.end()); + dolfin_assert(ms.is_valid()); + + return ms.radius(); +} + +} // end anonymous namespace +//----------------------------------------------------------------------------- +namespace mshr +{ + +TetgenMeshGenerator3D::TetgenMeshGenerator3D() +{ + parameters = default_parameters(); +} +//----------------------------------------------------------------------------- +TetgenMeshGenerator3D::~TetgenMeshGenerator3D() +{ +} +//----------------------------------------------------------------------------- +std::shared_ptr +TetgenMeshGenerator3D::generate(std::shared_ptr domain) const +{ + tetgenio in; + + double r; + { + // Copy the vertices to the tetgen structure + dolfin::log(dolfin::TRACE, "Copying vertices"); + std::unique_ptr> vertices = domain->get_vertices(); + r = bounding_sphere_radius(*vertices); + + in.numberofpoints = vertices->size()/3; + in.pointlist = new REAL[in.numberofpoints * 3]; + + for (std::size_t i = 0; i < vertices->size(); i++) + { + in.pointlist[i] = (*vertices)[i]; + } + } + + // Copy the facets to the tetgen structure + { + dolfin::log(dolfin::TRACE, "Copying facets"); + std::unique_ptr> facets = domain->get_facets(); + + in.numberoffacets = facets->size()/3; + in.facetlist = new tetgenio::facet[in.numberoffacets]; + //in.facetmarkerlist = new int[in.numberoffacets]; + + for (std::size_t i = 0; i*3 < facets->size(); i++) + { + tetgenio::facet& f = in.facetlist[i]; + f.numberofpolygons = 1; + f.polygonlist = new tetgenio::polygon[f.numberofpolygons]; + f.numberofholes = 0; + f.holelist = NULL; + tetgenio::polygon& p = f.polygonlist[0]; + p.numberofvertices = 3; + p.vertexlist = new int[p.numberofvertices]; + p.vertexlist[0] = (*facets)[i*3]; + p.vertexlist[1] = (*facets)[i*3+1]; + p.vertexlist[2] = (*facets)[i*3+2]; + } + } + + // Mark holes in the domain + { + dolfin::log(dolfin::TRACE, "Marking holes"); + std::vector holes; + domain->get_points_in_holes(holes); + + in.numberofholes = holes.size(); + in.holelist = new REAL[in.numberofholes*3]; + std::size_t i = 0; + for (std::vector::const_iterator it = holes.begin(); + it != holes.end(); it++) + { + in.holelist[i*3] = it->x(); + in.holelist[i*3 + 1] = it->y(); + in.holelist[i*3 + 2] = it->z(); + } + + i++; + } + + // Release domain object, possibly deleting it + domain.reset(); + + // set tetgen parameters + std::stringstream tetgenparams; + tetgenparams << std::fixed << std::setprecision(16); + + // tetrahedralize a plc + tetgenparams << "p"; + + if (!parameters["disable_quality_improvement"]) + { + // set quality constraints + const double ratio = parameters["max_radius_edge_ratio"]; + const double angle = parameters["min_dihedral_angle"]; + tetgenparams << "q" << ratio << "/" << angle; + + tetgenparams << "a"; + if (double(parameters["max_tet_volume"]) > 0) + { + // set maximum cell volume + tetgenparams << double(parameters["max_tet_volume"]); + } + else + { + const double resolution = parameters["mesh_resolution"]; + + // try to compute reasonable parameters + //const double r = bounding_sphere_radius(vertices); + const double cell_size = r/static_cast(resolution)*2.0; + tetgenparams << cell_size; + } + + if (parameters["preserve_surface"]) + { + tetgenparams << "Y"; + } + } + + if (dolfin::get_log_level() > dolfin::DBG) + { + // set verbosity level + tetgenparams << "Q"; + } + + dolfin::log(dolfin::TRACE, "Calling tetgen with parameters: " + tetgenparams.str()); + + // Tetgen requires a char[] (as opposed to a const char[]) + // so we need to copy of from the string + const std::string str = tetgenparams.str(); + std::unique_ptr writable(new char[str.size() + 1]); + std::copy(str.begin(), str.end(), writable.get()); + writable.get()[str.size()] = '\0'; // terminating 0 + + tetgenio out; + tetrahedralize(writable.get(), &in, &out); + + std::shared_ptr mesh(new dolfin::Mesh()); + build_dolfin_mesh(out, *mesh); + + // Distribute the mesh (if in parallel) + dolfin::MeshPartitioning::build_distributed_mesh(*mesh); + + return mesh; +} +//----------------------------------------------------------------------------- +} // end namespace mshr diff --git a/src/make_multicomponent_mesh_3.h b/src/make_multicomponent_mesh_3.h new file mode 100644 index 0000000..e0fc464 --- /dev/null +++ b/src/make_multicomponent_mesh_3.h @@ -0,0 +1,65 @@ +// Copyright (C) 2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . + +#ifndef __MSHR_MAKE_MULTICOMPONENT_MESH_3_H +#define __MSHR_MAKE_MULTICOMPONENT_MESH_3_H + +#include +#include + + +template +void make_multicomponent_mesh_3_impl(C3T3& c3t3, + const MeshDomain& domain, + const MeshCriteria& criteria, + const bool with_features) +{ + //std::cout << "Number of vertices initially: " << c3t3.triangulation().number_of_vertices() << std::endl; + + // Initialize c3t3 with points from the special features + CGAL::Mesh_3::internal::C3t3_initializer< + C3T3, + MeshDomain, + MeshCriteria, +#if CGAL_VERSION_NR >= 1050601000 + CGAL::internal::has_Has_features::value > () (c3t3, + domain, + criteria, + with_features); +#else + CGAL::Mesh_3::internal::has_Has_features::value > () (c3t3, + domain, + criteria, + with_features); +#endif + + // std::cout << "Number of vertices after features: " << c3t3.triangulation().number_of_vertices() << std::endl; + + // Inserts points from all connected components to the mesh + CGAL::Mesh_3::internal::init_c3t3(c3t3, domain, criteria, 0); + // std::cout << "Number of vertices before meshing: " << c3t3.triangulation().number_of_vertices() << std::endl; + + // Build mesher and launch refinement process + // Don't reset c3t3 as we just created it + refine_mesh_3(c3t3, domain, criteria, + CGAL::parameters::no_exude(), + CGAL::parameters::no_perturb(), + CGAL::parameters::no_odt(), + CGAL::parameters::no_lloyd()); +} + +#endif diff --git a/src/meshclean.h b/src/meshclean.h new file mode 100644 index 0000000..7ca75e3 --- /dev/null +++ b/src/meshclean.h @@ -0,0 +1,562 @@ +// Copyright (C) 2014-2015 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#ifndef __MESH_CLEAN_H +#define __MESH_CLEAN_H + +#include +#include +#include + +//----------------------------------------------------------------------------- +// Get squared edge length +template +inline double +get_edge_length(typename Polyhedron::Halfedge_const_handle halfedge) +{ + return CGAL::to_double((halfedge->vertex()->point() - + halfedge->opposite()->vertex()->point()).squared_length()); +} +//----------------------------------------------------------------------------- +template +inline double get_triangle_area(typename Polyhedron::Facet_handle facet) +{ + typedef typename Polyhedron::Traits::Triangle_3 Triangle; + typename Polyhedron::Halfedge_const_handle h = facet->halfedge(); + Triangle t(h->vertex()->point(), + h->next()->vertex()->point(), + h->next()->next()->vertex()->point()); + return CGAL::to_double(t.squared_area()); +} +//----------------------------------------------------------------------------- +template +inline typename Polyhedron::Halfedge_handle + get_longest_edge(typename Polyhedron::Facet_handle facet) +{ + typename Polyhedron::Halfedge_handle current = facet->halfedge(); + typename Polyhedron::Halfedge_handle longest = current; + double length = get_edge_length(current); + + current = current->next(); + if (get_edge_length(current) > length) + { + length = get_edge_length(current); + longest = current; + } + + current = current->next(); + if (get_edge_length(current) > length) + { + length = get_edge_length(current); + longest = current; + } + + return longest; +} +//----------------------------------------------------------------------------- +template +inline typename Polyhedron::Halfedge_const_handle + get_longest_const_edge(typename Polyhedron::Facet_const_handle facet) +{ + typename Polyhedron::Halfedge_const_handle current = facet->halfedge(); + typename Polyhedron::Halfedge_const_handle longest = current; + double length = get_edge_length(current); + + current = current->next(); + if (get_edge_length(current) > length) + { + length = get_edge_length(current); + longest = current; + } + + current = current->next(); + if (get_edge_length(current) > length) + { + length = get_edge_length(current); + longest = current; + } + + return longest; +} +//----------------------------------------------------------------------------- +// Compute the distance from the longest edge to the opposite vertex +template +inline double triangle_projection(typename Polyhedron::Facet_const_handle f) +{ + typename Polyhedron::Halfedge_const_handle longest = get_longest_const_edge(f); + typename Polyhedron::Traits::Line_3 l(longest->vertex()->point(), longest->opposite()->vertex()->point()); + return CGAL::to_double( (l.projection(longest->next()->vertex()->point()) - longest->next()->vertex()->point()).squared_length()); +} +//----------------------------------------------------------------------------- +template +inline double +get_min_edge_length(typename Polyhedron::Facet_const_handle facet) +{ + typename Polyhedron::Facet::Halfedge_around_facet_const_circulator half_edge + = facet->facet_begin(); + double min_length = CGAL::to_double((half_edge->vertex()->point() + - half_edge->opposite()->vertex()->point()).squared_length()); + + half_edge++; + min_length = std::min(min_length, get_edge_length(half_edge)); + + half_edge++; + min_length = std::min(min_length, get_edge_length(half_edge)); + + return min_length; +} +//----------------------------------------------------------------------------- +template +inline double +get_max_edge_length(typename Polyhedron::Facet_const_handle facet) +{ + typename Polyhedron::Facet::Halfedge_around_facet_const_circulator h = facet->facet_begin(); + double max_length = get_edge_length(h); + + h++; + max_length = std::max(max_length, get_edge_length(h)); + + h++; + max_length = std::max(max_length, get_edge_length(h)); + + return max_length; +} +//----------------------------------------------------------------------------- +template +std::size_t get_vertex_id(const Polyhedron& p, typename Polyhedron::Vertex_const_handle v) +{ + return std::distance(p.vertices_begin(), v); +} +//----------------------------------------------------------------------------- +template +void print_vertex(const Polyhedron& p, typename Polyhedron::Halfedge_const_handle h) +{ + typename Polyhedron::Halfedge_const_handle current = h; + do + { + dolfin_assert(current->vertex() == h->vertex()); + std::cout << get_vertex_id(p, current->opposite()->vertex()) << std::endl; + + current = current->opposite()->prev(); + } while(current != h); +} + +//----------------------------------------------------------------------------- +template +void print_edge(const Polyhedron& p, typename Polyhedron::Halfedge_const_handle h) +{ + std::cout << "(" << get_vertex_id(p, h->opposite()->vertex()) << ": " << h->opposite()->vertex()->point() << ", " + << get_vertex_id(p, h->vertex()) << ": " << h->vertex()->point() << ")" << std::endl; +} +//----------------------------------------------------------------------------- +// Print some info about a facet +// Meant for debugging +template +void print_facet(const Polyhedron& p, typename Polyhedron::Halfedge_const_handle h, bool verbose=true) +{ + std::cout << "Vertices: " << std::endl; + typename Polyhedron::Halfedge_const_handle current = h; + do + { + std::cout << " " << get_vertex_id(p, current->vertex()) << ": (" << current->vertex()->point() << ") " << std::endl; + current = current ->next(); + } while (current != h); + + if (verbose) + { + std::cout << "Edge lengths (squared):" << std::endl; + current = h; + do + { + std::cout << " " << (current->vertex()->point() - current->next()->vertex()->point()).squared_length() << std::endl; + current = current->next(); + } while (current != h); + + typename Polyhedron::Traits::Triangle_3 t(h->vertex()->point(), + h->next()->vertex()->point(), + h->next()->next()->vertex()->point()); + std::cout << "Area (if triangle): " << t.squared_area() << std::endl; + + typename Polyhedron::Halfedge_const_handle longest = get_longest_const_edge(h->facet()); + typename Polyhedron::Traits::Line_3 l(longest->vertex()->point(), longest->opposite()->vertex()->point()); + std::cout << "Colinearity (if triangle): " << triangle_projection(h->facet()) << std::endl; + } +} +//----------------------------------------------------------------------------- +template +inline bool facet_is_degenerate(typename Polyhedron::Facet_const_handle facet, + typename Polyhedron::Traits::FT tol_sq) +{ + dolfin_assert(facet->is_triangle()); + + return get_min_edge_length(facet) < tol_sq + || triangle_projection(facet) < tol_sq; +} +//----------------------------------------------------------------------------- +template +inline double shortest_edge(Polyhedron& p) +{ + double shortest = std::numeric_limits::max(); + for (typename Polyhedron::Halfedge_iterator halfedge = p.halfedges_begin(); + halfedge != p.halfedges_end(); halfedge++) + { + const double length = get_edge_length(halfedge); + shortest = std::min(shortest, length); + } + + return shortest; +} +//----------------------------------------------------------------------------- +template +int number_of_degenerate_facets(const Polyhedron& p, const double tolerance) +{ + int count = 0; + for (typename Polyhedron::Facet_const_iterator facet = p.facets_begin(); + facet != p.facets_end(); facet++) + { + dolfin_assert(facet->is_triangle()); + if ( facet_is_degenerate(facet, tolerance) ) + count++; + } + return count; +} +//----------------------------------------------------------------------------- +template +inline bool has_slivers(const Polyhedron& p) +{ + for (typename Polyhedron::Vertex_const_iterator vit = p.vertices_begin(); vit != p.vertices_end(); vit++) + { + if (vit->vertex_degree() < 3) + { + // Check that this is not on a border (we can have borders eg. when + // reading files that must be repaired + const typename Polyhedron::Halfedge_around_vertex_const_circulator first = vit->vertex_begin(); + typename Polyhedron::Halfedge_around_vertex_const_circulator current = first; + + bool is_border = false; + + do + { + if ( !current->is_border() ) + is_border = true; + + current++; + } while (current != first); + + if (!is_border) + return true; + } + } + + /* for (typename Polyhedron::Halfedge_const_iterator it = p.halfedges_begin(); */ + /* it != p.halfedges_end(); it++) */ + /* { */ + /* if (it->next()->vertex() == it->opposite()->next()->vertex()) */ + /* { */ + /* return true; */ + /* } */ + /* } */ + return false; +} +//----------------------------------------------------------------------------- +template +inline bool has_degree3_neighbors(const Polyhedron& p) +{ + for (typename Polyhedron::Halfedge_const_iterator it = p.halfedges_begin(); + it != p.halfedges_end(); it++) + { + if (it->vertex()->vertex_degree() < 4 && + it->opposite()->vertex()->vertex_degree() < 4) + return true; + } + + return false; +} +//----------------------------------------------------------------------------- +template +inline std::size_t min_vertex_degree(const Polyhedron& p) +{ + typename Polyhedron::Vertex_const_iterator vit = p.vertices_begin(); + std::size_t min_degree = vit->vertex_degree(); + vit++; + + for (; vit != p.vertices_end(); vit++) + min_degree = std::min(min_degree, vit->vertex_degree()); + + return min_degree; +} +//----------------------------------------------------------------------------- +#define ASSERT_GOOD_STATE(p) do \ +{ \ + dolfin_assert(p.is_valid()); \ + dolfin_assert(p.is_pure_triangle()); \ + dolfin_assert(!has_slivers(p)); \ + dolfin_assert(p.is_closed()); \ + dolfin_assert(min_vertex_degree(p) > 2); \ + dolfin_assert(p.size_of_vertices() < 5 || !has_degree3_neighbors(p)); \ +} while(false); +//----------------------------------------------------------------------------- +template +inline void remove_degree3_center_vertex(Polyhedron& p, + typename Polyhedron::Halfedge_handle h) +{ + // Remove center vertex, but assure the degree of the vertex is 3 and that at least + // one of the sides of the incident triangles is short + dolfin_assert(h->vertex()->vertex_degree() == 3); + + p.erase_center_vertex(h); +} +//----------------------------------------------------------------------------- +template +inline bool remove_degree3_with_short_edges(Polyhedron& p, + typename Polyhedron::Traits::FT tol_sq) +{ + bool removed = false; + + typename Polyhedron::Vertex_iterator vit = p.vertices_begin(); + typename Polyhedron::Vertex_iterator vertex_end = p.vertices_end(); + while (vit != vertex_end) + { + // iterator is invalid if we remove the vertex, so copy and advance now + typename Polyhedron::Vertex_handle v = vit; + vit++; + + if (v->vertex_degree() < 3) + log(dolfin::DBG, "Vertex with degree less than 3 detected"); + + if (v->is_trivalent()) + { + typename Polyhedron::Halfedge_handle h = v->halfedge(); + if (get_edge_length(h) < tol_sq || + get_edge_length(h->next()) < tol_sq || + get_edge_length(h->prev()) < tol_sq) + { + p.erase_center_vertex(h); + removed = true; + } + } + } + return removed; +} +//----------------------------------------------------------------------------- +template +inline void collapse_edge(Polyhedron& p, + typename Polyhedron::Halfedge_handle edge) +{ + ASSERT_GOOD_STATE(p); + + if (edge->vertex()->is_trivalent()) + { + p.erase_center_vertex(edge); + ASSERT_GOOD_STATE(p); + } + else if (edge->opposite()->vertex()->is_trivalent()) + { + p.erase_center_vertex(edge->opposite()); + ASSERT_GOOD_STATE(p); + } + else + { + // Join small triangles with neighbor facets + + // Make sure we don't introduce slivers + // (ie. vertices of degree 2) + while (edge->next()->vertex()->is_trivalent()) + { + remove_degree3_center_vertex(p, edge->next()); + ASSERT_GOOD_STATE(p); + } + + while (edge->opposite()->next()->vertex()->is_trivalent()) + { + remove_degree3_center_vertex(p, edge->opposite()->next()); + ASSERT_GOOD_STATE(p); + } + + if (edge->vertex()->is_trivalent()) + { + p.erase_center_vertex(edge); + } + else if (edge->opposite()->vertex()->is_trivalent()) + { + p.erase_center_vertex(edge->opposite()); + } + + ASSERT_GOOD_STATE(p); + dolfin_assert(edge->vertex()->vertex_degree() > 3); + + edge = p.join_facet(edge->next()); + dolfin_assert(p.is_valid()); + dolfin_assert(!has_slivers(p)); + + p.join_facet(edge->opposite()->prev()); + dolfin_assert(p.is_valid()); + + dolfin_assert(edge->vertex() != edge->opposite()->vertex()); + + // We can possibly have a sliver now + + // The joined facets are now quads + // Join the two close vertices + p.join_vertex(edge); + ASSERT_GOOD_STATE(p); + } +} +//----------------------------------------------------------------------------- +// FIXME: Return the number of edges collapsed +template +bool collapse_short_edges(Polyhedron& p, + typename Polyhedron::Traits::FT tol_sq) +{ + // Degree 3 vertices with short incident edges causes problems when collapsing + // short edges. The very ad hoc solution that has shown to work is to remove + // these center vertices (and by that the short edges) before collapsing short + // edges. + + bool edges_removed = remove_degree3_with_short_edges(p, tol_sq); + + bool removed; + + do + { + removed = false; + + for (typename Polyhedron::Halfedge_iterator halfedge = p.halfedges_begin(); + halfedge != p.halfedges_end(); halfedge++) + { + if (get_edge_length(halfedge) < tol_sq) + { + collapse_edge(p, halfedge); + ASSERT_GOOD_STATE(p); + remove_degree3_with_short_edges(p, tol_sq); + removed = true; + edges_removed = true; + break; + } + } + } while (removed); + + return edges_removed; +} +//----------------------------------------------------------------------------- +template +bool flip_edges(Polyhedron& p, + typename Polyhedron::Traits::FT tol_sq) +{ + typedef typename Polyhedron::Traits::Line_3 Line_3; + typedef typename Polyhedron::Traits::Point_3 Point_3; + typedef typename Polyhedron::Halfedge_handle Halfedge_handle; + + bool edge_flipped = false; + bool done; + + do + { + done = true; + + for (typename Polyhedron::Facet_iterator facet = p.facets_begin(); + facet != p.facets_end(); facet++) + { + dolfin_assert(facet->is_triangle()); + if (triangle_projection(facet) < tol_sq) + { + dolfin_assert(p.is_pure_triangle()); + dolfin_assert(p.is_closed()); + + typename Polyhedron::Halfedge_handle longest + = get_longest_edge(facet); + + if (longest->vertex()->is_trivalent()) + remove_degree3_center_vertex(p, longest); + else if (longest->opposite()->vertex()->is_trivalent()) + remove_degree3_center_vertex(p, longest->opposite()); + else + { + + Line_3 l(longest->vertex()->point(), + longest->opposite()->vertex()->point()); + Point_3 newpoint = l.projection(longest->next()->vertex()->point()); + Halfedge_handle flipped = p.flip_edge(longest); + flipped->vertex()->point() = newpoint; + + ASSERT_GOOD_STATE(p); + + // TODO: Check length of newly created edge and + // collapse if necessary. + collapse_short_edges(p, tol_sq); + done = false; + edge_flipped = true; + } + } + } + } while (!done); + + return edge_flipped; +} +//----------------------------------------------------------------------------- +template +bool has_degenerate_facets(const Polyhedron& p, + typename Polyhedron::Traits::FT tol_sq) +{ + for (typename Polyhedron::Facet_const_iterator facet = p.facets_begin(); + facet != p.facets_end(); facet++) + { + dolfin_assert(facet->is_triangle()); + if (facet_is_degenerate(facet, tol_sq)) + return true; + } + return false; +} +//----------------------------------------------------------------------------- +// Remove degenerate facets of a triangular polyhedron by +// 1) Collapse edges with squared length less than tolerance +// 2) Remove (almost) colinear facets by flipping the longest edge of the +// colinear facet. Colinearity defined as the (squared) distance from a +// vertex to the opposite edge being less than tolerance +template +bool remove_degenerate(Polyhedron &p, double tolerance) +{ + log(dolfin::TRACE, "Cleaning degenerate facets"); + dolfin_assert(p.is_pure_triangle()); + + // Compute squared tolerance + typename Polyhedron::Traits::FT tol_sq(tolerance); + tol_sq *= tol_sq; + + const bool edges_removed = remove_degree3_with_short_edges(p, tol_sq); + if (edges_removed) + log(dolfin::TRACE, "Remove degree 3 vertices"); + + ASSERT_GOOD_STATE(p); + + + log(dolfin::TRACE, " Collapsing short edges"); + const bool collapsed = collapse_short_edges(p, tol_sq); + ASSERT_GOOD_STATE(p); + + // log(dolfin::TRACE, "Shortest edge: %f", shortest_edge()); + log(dolfin::TRACE, " Removing colinear facets by edge flipping"); + const bool flipped = flip_edges(p, tol_sq); + ASSERT_GOOD_STATE(p); + + dolfin_assert(!has_degenerate_facets(p, tol_sq)); + + return collapsed || flipped; +} + +#endif diff --git a/src/smoothing.h b/src/smoothing.h new file mode 100644 index 0000000..63a89c0 --- /dev/null +++ b/src/smoothing.h @@ -0,0 +1,73 @@ +// Copyright (C) 2016 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#ifndef __SMOOTHING_H +#define __SMOOTHING_H + +namespace mshr +{ + +class LaplacianSmoothing +{ + public: + template + static void smooth(Polyhedron& p, double c=1.0) + { + typedef typename Polyhedron::Vertex_handle Vertex_handle; + typedef typename Polyhedron::Traits::Point_3 Point_3; + typedef typename Polyhedron::Traits::Vector_3 Vector_3; + typedef typename Polyhedron::Halfedge_around_vertex_const_circulator HV_const_circulator; + + assert(p.is_valid()); + + std::vector> smoothed; + + for (typename Polyhedron::Vertex_iterator vit = p.vertices_begin(); + vit != p.vertices_end(); + vit++) + { + const Point_3 current = vit->point(); + + Vector_3 delta; + + const HV_const_circulator h_start = vit->vertex_begin(); + HV_const_circulator h_current = h_start; + do + { + delta = delta + (h_current->opposite()->vertex()->point()-current); + h_current++; + } while (h_current != h_start); + + Point_3 p = current + c*delta/vit->vertex_degree(); + + // Evaluate exact value to reduce memory usage + p.exact(); + + smoothed.push_back(std::make_pair(Vertex_handle(vit), p)); + } + + // Apply the smoothing + for (const std::pair& s : smoothed) + { + s.first->point() = s.second; + } + } +}; + +} +#endif diff --git a/src/triangulation_refinement.h b/src/triangulation_refinement.h new file mode 100644 index 0000000..5c48ec5 --- /dev/null +++ b/src/triangulation_refinement.h @@ -0,0 +1,318 @@ +// Copyright (C) 2014 Benjamin Kehlet +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#ifndef __TRIANGULATION_REFINEMENT_H +#define __TRIANGULATION_REFINEMENT_H + +#include + +#ifndef DEBUG_TRIANGULATION_REFINEMENT + #define DEBUG_TRIANGULATION_REFINEMENT 0 +#endif + +namespace +{ + // Simple convenience class for converting from barycentric coordinates wrt to + // a reference triangle. + class RefTriangle + { + public: + RefTriangle(dolfin::Point v1, dolfin::Point v2, dolfin::Point v3) + : v1(v1), v2(v2), v3(v3) {} + + dolfin::Point barycentric2Point(double l1, double l2, double l3) + { + const double x = l1*v1.x() + l2*v2.x() + l3*v3.x(); + const double y = l1*v1.y() + l2*v2.y() + l3*v3.y(); + const double z = l1*v1.z() + l2*v2.z() + l3*v3.z(); + + return dolfin::Point(x, y, z); + } + + private: + dolfin::Point v1, v2 , v3; + }; + //----------------------------------------------------------------------------- + inline dolfin::Point get_edge_point(dolfin::Point a, dolfin::Point b, double f) + { + // print "Get point {} between {} and {}".format(f, a, b) + const dolfin::Point e = b-a; + return a + e*f; + } + //----------------------------------------------------------------------------- + inline std::size_t get_edge_vertex(const std::map, + std::size_t>& edge_vertices, + std::size_t a, + std::size_t b, + std::size_t i, + std::size_t N) + { + const std::array key{{std::min(a, b), std::max(a, b), a < b ? i : N-i}}; + std::size_t v = edge_vertices.at(key); + // print "getting edge vertex ({}, {}, {}) = {}".format(a, b, i, v) + return v; + } + //----------------------------------------------------------------------------- + inline void add_cell(std::vector >& triangles, std::array v) + { + #if DEBUG_TRIANGULATION_REFINEMENT + static std::set > halfedges; + + auto ins = halfedges.insert(std::make_pair(v[0], v[1])); + dolfin_assert(ins.second); + ins = halfedges.insert(std::make_pair(v[1], v[2])); + dolfin_assert(ins.second); + ins = halfedges.insert(std::make_pair(v[2], v[0])); + dolfin_assert(ins.second); + #endif + + triangles.push_back(v); + } +} +//----------------------------------------------------------------------------- +// TODO: Use outputiterator to return triangulation instead of vectors to avoid +// copying +void refine_triangulation(const std::vector initial_vertices, + const std::vector > initial_triangulation, + std::size_t N, + std::vector& vertices, + std::vector >& triangles) +{ + const std::size_t num_vertices = initial_vertices.size() + + (N-1)*initial_triangulation.size() + (N+1)*N/2.0; + const std::size_t num_triangles = N*N*initial_triangulation.size(); + + vertices.clear(); + triangles.clear(); + vertices.reserve(num_vertices); + triangles.reserve(num_triangles); + + // Add the corner vertices + for (const dolfin::Point& p : initial_vertices) + { + vertices.push_back(p); + } + + std::map, std::size_t> edge_vertices; + + // Add the "inner" vertices along the edges + for (std::size_t i=1; i < N; i++) + { + for (const std::array& t : initial_triangulation) + { + for (std::size_t j = 0; j < 3; j++) + { + if (t[j] < t[(j+1)%3]) + { + dolfin::Point v = get_edge_point(initial_vertices[t[j]], initial_vertices[t[(j+1)%3]], float(i)/N); + + // NOTE: Some older compilers (eg. gcc on Ubuntu Precise) require the std::array type to be given + // explicitly. + edge_vertices[std::array{{t[j], t[(j+1)%3], i}}] = vertices.size(); + vertices.push_back(v/v.norm()); + } + } + } + } + + + /***************************** Add the triangles *********************************/ + std::size_t cell_count = 0; + for (const std::array& triangle : initial_triangulation) + { + //std::cout << "Processing triangle (" << triangle[0] << ", " << triangle[1] << ", " << triangle[2] << ")" << std::endl; + RefTriangle ref_triangle(initial_vertices[triangle[0]], initial_vertices[triangle[1]], initial_vertices[triangle[2]]); + const std::size_t vertex_start = vertices.size(); + + for (std::size_t i = 1; i < N; i++) + { + const double l1 = static_cast(i)/N; + for (std::size_t j=1; j < N-i; j++) + { + // Don't edge along initial vertices + if (i+j == N) + continue; + + const double l2 = static_cast(j)/N; + const double l3 = 1.0 - l1 - l2; + + dolfin::Point p = ref_triangle.barycentric2Point(l1, l2, l3); + p /= p.norm(); + vertices.push_back(p); + } + } + + // std::cout << " Adding corner facets" << std::endl; + add_cell(triangles, std::array{{ triangle[0], + get_edge_vertex(edge_vertices, + triangle[0], + triangle[1], + 1, N), + get_edge_vertex(edge_vertices, + triangle[0], triangle[2], + 1, N)}}); + cell_count += 1; + + add_cell(triangles, std::array{{triangle[1], + get_edge_vertex(edge_vertices, + triangle[1], + triangle[2], + 1, N), + get_edge_vertex(edge_vertices, + triangle[1], + triangle[0], + 1, N)}}); + cell_count += 1; + + add_cell(triangles, std::array{{triangle[2], + get_edge_vertex(edge_vertices, + triangle[2], + triangle[0], + 1, N), + get_edge_vertex(edge_vertices, + triangle[2], + triangle[1], + 1, N)}}); + cell_count += 1; + + + if (N == 2) + { + // std::cout << " Refinement level 2: Adding interior cell" << std::endl; + add_cell(triangles, std::array{{get_edge_vertex(edge_vertices, triangle[0], triangle[1], 1, N), + get_edge_vertex(edge_vertices, triangle[1], triangle[2], 1, N), + get_edge_vertex(edge_vertices, triangle[2], triangle[0], 1, N)}}); + cell_count += 1; + } + else + { + // std::cout << " Add facets incident to original edges" << std::endl; + add_cell(triangles, std::array{{vertex_start, + get_edge_vertex(edge_vertices, triangle[2], triangle[1], 1, N), + get_edge_vertex(edge_vertices, triangle[2], triangle[0], 1, N)}}); + cell_count += 1; + + add_cell(triangles, std::array{{vertex_start+N-3, + get_edge_vertex(edge_vertices, triangle[1], triangle[0], 1, N), + get_edge_vertex(edge_vertices, triangle[1], triangle[2], 1, N)}}); + cell_count += 1; + + add_cell(triangles, std::array{{ vertices.size() - 1, + get_edge_vertex(edge_vertices,triangle[0], triangle[2], 1, N), + get_edge_vertex(edge_vertices,triangle[0], triangle[1], 1, N)}}); + cell_count += 1; + + + // std::cout << " Add the facets along the original edges" << std::endl; + + // along edge(triangle[1] <--> triangle[2]) + add_cell(triangles, std::array{{vertex_start, + get_edge_vertex(edge_vertices,triangle[2], triangle[1], 2, N), + get_edge_vertex(edge_vertices,triangle[2], triangle[1], 1, N)}}); + cell_count += 1; + + for (std::size_t i = 3; i < N; i++) + { + add_cell(triangles, std::array{{vertex_start+i-3, + vertex_start+i-2, + get_edge_vertex(edge_vertices,triangle[2], triangle[1], i-1, N)}}); + cell_count += 1; + + add_cell(triangles, std::array{{vertex_start+i-2, + get_edge_vertex(edge_vertices,triangle[2], triangle[1], i, N), + get_edge_vertex(edge_vertices,triangle[2], triangle[1], i-1, N)}}); + cell_count += 1; + } + + // along edge(triangle[2], triangle[0]) + add_cell(triangles, std::array{{vertex_start, + get_edge_vertex(edge_vertices,triangle[2], triangle[0], 1, N), + get_edge_vertex(edge_vertices,triangle[2], triangle[0], 2, N)}}); + cell_count += 1; + + std::size_t current_inner_vertex = 0; + for (std::size_t i = 3; i < N; i++) + { + const std::size_t step = N-2-i+3; + add_cell(triangles, std::array{{vertex_start+current_inner_vertex+step, + vertex_start+current_inner_vertex, + get_edge_vertex(edge_vertices,triangle[2], triangle[0], i-1, N)}}); + cell_count += 1; + + add_cell(triangles, std::array{{get_edge_vertex(edge_vertices,triangle[2], triangle[0], i-1, N), + get_edge_vertex(edge_vertices,triangle[2], triangle[0], i, N), + vertex_start+current_inner_vertex+step}}); + cell_count += 1; + + current_inner_vertex += step; + } + + // along edge(triangle[1], triangle[0]) + add_cell(triangles, std::array{{vertex_start+N-3, + get_edge_vertex(edge_vertices,triangle[1], triangle[0], 2, N), + get_edge_vertex(edge_vertices,triangle[1], triangle[0], 1, N)}}); + cell_count += 1; + + current_inner_vertex = N-3; + for (std::size_t i = 3; i < N; i++) + { + const std::size_t step = N-3-i+3; + add_cell(triangles, std::array{{vertex_start+current_inner_vertex, + vertex_start+current_inner_vertex+step, + get_edge_vertex(edge_vertices,triangle[1], triangle[0], i-3+2, N)}}); + cell_count += 1; + + add_cell(triangles, std::array{{vertex_start+current_inner_vertex+step, + get_edge_vertex(edge_vertices,triangle[1], triangle[0], i-3+3, N), + get_edge_vertex(edge_vertices,triangle[1], triangle[0], i-3+2, N)}}); + cell_count += 1; + + current_inner_vertex += step; + } + } + + // std::cout << " Add the inner facets that don't touch initial edges" << std::endl; + + std::size_t row_offset = 0; + for (std::size_t i = 1; i < N-2; i++) + { + const std::size_t row_length = N-1-i; + add_cell(triangles, std::array{{vertex_start+row_offset, + vertex_start+row_offset+row_length, + vertex_start+row_offset+1}}); + cell_count += 1; + + for (std::size_t j = 0; j < N-3-i; j++) + { + add_cell(triangles, + std::array{{vertex_start+row_offset+row_length+j, + vertex_start+row_offset+row_length+j+1, + vertex_start+row_offset+j+1}}); + cell_count += 1; + + add_cell(triangles, std::array{{vertex_start+row_offset+row_length+j+1, + vertex_start+row_offset+j+2, + vertex_start+row_offset+j+1}}); + cell_count += 1; + } + row_offset += row_length; + } + } +} + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..902248b --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,120 @@ +# Copyright (C) 2014 Benjamin Kehlet +# +# This file is part of mshr. +# +# mshr is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# mshr is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with mshr. If not, see . + +# This file defines regression tests. + +# Make sure python finds the mshr module. +# NOTE: Not sure this is the best way. Is saves the environment at "cmake +# time" and applies it when running the test. Changing PYTHONPATH +# later will not have effect. Not sure how to add tp PYTHONPATH at +# runtime. +set(PYTHON_ENVIR "$ENV{PYTHONPATH};DOLFIN_NOPLOT=True") + + +############# A python program that just imports dolfin and mshr ############################## +add_test("Python-DummyImport" "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/test/dummy.py") +set_property(TEST "Python-DummyImport" PROPERTY + ENVIRONMENT "PYTHONPATH=${PYTHON_ENVIR}" +) + +############# A union which is particularly prone to roundoff errors ########################## +add_test("Python-DegenerateRemoval" "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/test/degenerate_removal.py") +set_property(TEST "Python-DegenerateRemoval" PROPERTY + ENVIRONMENT "PYTHONPATH=${PYTHON_ENVIR}" +) + +############# Test the csg operators ########################################################## +add_test("Python-CSGOperators" "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/test/test-csg.py") +set_property(TEST "Python-CSGOperators" PROPERTY + ENVIRONMENT "PYTHONPATH=${PYTHON_ENVIR}" +) + +############# Test the fuzzy point map ######################################################## +# This test is disabled since the Surface3D class is about to be removed. +#add_test("Python-FuzzyPointMap" "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/test/test-fuzzypointmap.py") +#set_property(TEST "Python-FuzzyPointMap" PROPERTY +# ENVIRONMENT "PYTHONPATH=${PYTHON_ENVIR}" +#) + +############# Test the mesh generation ######################################################## +add_test("Python-MeshGeneration" "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/test/test-mesh-generation.py") +set_property(TEST "Python-MeshGeneration" PROPERTY + ENVIRONMENT "PYTHONPATH=${PYTHON_ENVIR}" +) + +############# Test the predefined meshes ########################################################## +add_test("Python-Meshes" "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/test/test-meshes.py") +set_property(TEST "Python-Meshes" PROPERTY + ENVIRONMENT "PYTHONPATH=${PYTHON_ENVIR}" +) +############# Test the ASCFileReader ########################################################## +add_test("Python-ASCFileReader" "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/test/test-ASCFileReader.py") +set_property(TEST "Python-ASCFileReader" PROPERTY + ENVIRONMENT "PYTHONPATH=${PYTHON_ENVIR}" +) + +############# Test the ASCFileReader ########################################################## +add_test("Python-NumSegments2D" "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/test/test-num-segments-2d.py") +set_property(TEST "Python-NumSegments2D" PROPERTY + ENVIRONMENT "PYTHONPATH=${PYTHON_ENVIR}" +) + +############# Test the CSG predicates (2D) ########################################################## +add_test("Python-CSGPredicates2D" "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/test/test-csg-predicates.py") +set_property(TEST "Python-CSGPredicates2D" PROPERTY + ENVIRONMENT "PYTHONPATH=${PYTHON_ENVIR}" + ) + +############# Test CSG primitves (2D) ########################################################## +add_test("Python-CSGPrimitives2D" "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/test/test-csg-primitives-2d.py") +set_property(TEST "Python-CSGPredicates2D" PROPERTY + ENVIRONMENT "PYTHONPATH=${PYTHON_ENVIR}" +) + +############# Try meshing some surface files found in the cgal source ######################## +# TODO: Print mesh quality measure from program and check the value +set(CGAL_DATA_DIR "${CMAKE_SOURCE_DIR}/3rdparty/CGAL/demo/Polyhedron/data") + +# Files that should pass +# Note elephant.off is valid a should passes but takes a very long (so test slave may time out) +set(TESTFILES_VALID "anchor.off;couplingdown.off;cross.off;cube.off;dragknob.off;ellipsoid.off;handle.off;icosahedron.off;joint.off;knot1.off;knot2.off;pinion.off;pipe.off;rotor.off;sphere.off;spool.off;star.off;translated-cube.off;tripod.off") +set(TESTFILES_SELFINTERSECTING "bones.off;cow.off;man.off;oblong.ogg") +set(TESTFILES_NOTCLOSED "cube-ouvert.off;mushroom.off") +set(TESTFILES_NOTTRIANGULAR "pyramid.off") + +foreach(CURRENT_DATA_FILE IN LISTS TESTFILES_VALID) + message(STATUS "Adding test: ${CURRENT_DATA_FILE}") + add_test(FilePass-cgal-${CURRENT_DATA_FILE} ../mshrable -s -b cgal --check-mesh ${CGAL_DATA_DIR}/${CURRENT_DATA_FILE}) + add_test(FilePass-tetgen-${CURRENT_DATA_FILE} ../mshrable -s -b tetgen --check-mesh ${CGAL_DATA_DIR}/${CURRENT_DATA_FILE}) +endforeach(CURRENT_DATA_FILE) + + +############## CPP tests +add_executable(cppTestCSGGeometries test-csggeometries.cpp) +target_link_libraries(cppTestCSGGeometries mshr) +add_test(CPP-CSGGeometries cppTestCSGGeometries) + + +############ Run the demos as regression test ################################################ +file(GLOB PYTHONDEMOS "${CMAKE_SOURCE_DIR}/demo/python/*.py") +foreach(CURRENT_DEMO IN LISTS PYTHONDEMOS) + add_test("PythonDemo-${CURRENT_DEMO}" "${PYTHON_EXECUTABLE}" "${CURRENT_DEMO}") + set_property(TEST "PythonDemo-${CURRENT_DEMO}" PROPERTY + ENVIRONMENT "PYTHONPATH=${PYTHON_ENVIR}" + ) + +endforeach() diff --git a/test/degenerate_removal.py b/test/degenerate_removal.py new file mode 100644 index 0000000..84eff52 --- /dev/null +++ b/test/degenerate_removal.py @@ -0,0 +1,14 @@ +from mshr import * + +# This union of spheres challenges the removal of degenerate facets +# since the intersection polyline of the two spheres matches lines in +# the triangulation of the spheres. Because of that roundoff errors +# introduces a lot of very small triangles when the union is carried +# out. + +a = Sphere(dolfin.Point(0,0,0), .5) +b = Sphere(dolfin.Point(.5,0,0), .5) + +domain = CSGCGALDomain3D(a+b) +domain.ensure_meshing_preconditions() + diff --git a/test/dummy.py b/test/dummy.py new file mode 100644 index 0000000..d85bf5a --- /dev/null +++ b/test/dummy.py @@ -0,0 +1,5 @@ +import dolfin +import mshr + + + diff --git a/test/test-ASCFileReader.py b/test/test-ASCFileReader.py new file mode 100644 index 0000000..5d081da --- /dev/null +++ b/test/test-ASCFileReader.py @@ -0,0 +1,23 @@ +from dolfin import * +from mshr import * + +import os +import tempfile + +def save_surface(filename) : + geometry = Sphere(Point(0.0,0.0,0.0),10.) + domain = CSGCGALDomain3D(geometry) + domain.save(filename) + +def load_surface(filename) : + surf = Surface3D(filename) + domain = CSGCGALDomain3D(surf) + mesh = generate_mesh(domain, 10) + +fd, temp_path = tempfile.mkstemp(suffix='.asc') + +save_surface(temp_path) +load_surface(temp_path) + +os.close(fd) +os.remove(temp_path) diff --git a/test/test-csg-predicates.py b/test/test-csg-predicates.py new file mode 100644 index 0000000..7970161 --- /dev/null +++ b/test/test-csg-predicates.py @@ -0,0 +1,38 @@ +from dolfin import * +import mshr +import math + + +r = mshr.Rectangle(Point(0,-.5), Point(4,.5)) + +# Trivial inside outside +assert r.inside(Point( .5, .0)) +assert not r.inside(Point(1.5, 1.5)) + +rotated = mshr.CSGRotation(r, math.pi/2) +assert not rotated.inside(Point(1.5, 1.5)) +assert rotated.inside(Point(0., 1.5)) +assert not rotated.inside(Point(0., 4.5)) +assert not rotated.inside(Point(0., -.5)) + +# Translation +translated = mshr.CSGTranslation(rotated, Point(2, 1)) +assert translated.inside(Point(2, 1.5)) +assert not translated.inside(Point(0, 1.5)) + +# Circle +c = mshr.Circle(Point(2, 1), .5) +assert c.inside(Point(2, 1)) +assert not c.inside(Point(3, 1)) + +c_rotated = mshr.CSGRotation(c, Point(2,1), math.pi) +assert c_rotated.inside(Point(2,1)) +assert not c_rotated.inside(Point(3, 1)) + +c_rotated_2 = mshr.CSGRotation(c_rotated, pi/2) +assert not c_rotated_2.inside(Point(2,1)) +assert c_rotated_2.inside(Point(-1,2)) + +# Rectangle and box with the vertices not ordered +assert mshr.Rectangle(Point(0,0),Point(-1,-1)).inside(Point(-0.5,-0.5)) +#assert mshr.Box(Point(0,0,0),Point(-1,-1,-1)).inside(Point(-0.5,-0.5,-0.5)) diff --git a/test/test-csg-primitives-2d.py b/test/test-csg-primitives-2d.py new file mode 100644 index 0000000..bb09da1 --- /dev/null +++ b/test/test-csg-primitives-2d.py @@ -0,0 +1,9 @@ +from dolfin import * +from mshr import * + +# Polygon with list of points as argument +p = Polygon([Point(0, 0), Point(1, 1), Point(0, 1)]) + +# Polygon with tuple of points as argument +p = Polygon( (Point(0, 0), Point(1, 1), Point(0, 1)) ) + diff --git a/test/test-csg.py b/test/test-csg.py new file mode 100644 index 0000000..beae7e5 --- /dev/null +++ b/test/test-csg.py @@ -0,0 +1,39 @@ +from mshr import * +from dolfin import * +import math + +epsilon = 1e-11 + +def test_volume_exact(g, exact) : + domain = CSGCGALDomain3D(g) + vol = domain.volume() + assert abs(vol-exact) < epsilon, "Computed volume of {} was {}, but expected {}.".format(g.str(False), vol, exact) + +def test_volume_bounds(g, lower, upper) : + domain = CSGCGALDomain3D(g) + vol = domain.volume() + assert vol <= upper and vol >= lower, "Computed volume of {} was {}, but expected to be in [{}, {}]".format(g.str(False), vol, lower, upper) + + +### Test volume of primitives +# Sphere +test_volume_bounds(Sphere(Point(1,1,1), 1), 3.8/3.*math.pi, 4./3.0*math.pi) + +# TODO: Add primitives + +# Test union +test_volume_exact(Box(Point(0,0,0), Point(2,2,2)) + Box(Point(-1,-1,-1), Point(1,1,1)), 15) + +# TODO: Add operators + +# Test translation +c = Cylinder(Point(-1, 1, 2), Point(3.5, -1, 2), 2, 3) +test_volume_exact(c, CSGCGALDomain3D(CSGTranslation(c, Point(2, 4, -5))).volume()) + +# Test rotation +e = Ellipsoid(Point(1,2,3), 2,4,6) +test_volume_exact(e, CSGCGALDomain3D(CSGRotation(e, Point(1,1,1), math.pi/2.)).volume()) + +# Test scaling +s = Sphere(Point(0,0,0), 2) +test_volume_exact(CSGScaling(s, 2), CSGCGALDomain3D(s).volume()*8) diff --git a/test/test-csggeometries.cpp b/test/test-csggeometries.cpp new file mode 100644 index 0000000..8230400 --- /dev/null +++ b/test/test-csggeometries.cpp @@ -0,0 +1,30 @@ +// Copyright (C) 2017 Benjamin Kehlet +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with DOLFIN. If not, see . +// + +#include +#include +#include + +int main(int /* argc */, char** /* argv */) +{ + std::shared_ptr m = + std::make_shared( dolfin::Point(1., 2.), dolfin::Point(2., 4.), + 5, 6); + + std::shared_ptr g = mshr::CSGGeometries::import_mesh(m); + + return 0; +} diff --git a/test/test-fuzzypointmap.py b/test/test-fuzzypointmap.py new file mode 100644 index 0000000..8067a4f --- /dev/null +++ b/test/test-fuzzypointmap.py @@ -0,0 +1,108 @@ +import mshr +import tempfile, os, sys + +# A simple unit cube but with some vertices perturbed slightly to test the FuzzyPointMap +cube = """ +solid ascii +facet normal 0 0 0 + outer loop + vertex 1e-12 0 1 + vertex 0 1 0 + vertex 0 -1e-11 0 + endloop +endfacet + +facet normal 0 0 0 +outer loop + vertex 0 0 1 + vertex 0 1 1 + vertex 0 1 1e-13 +endloop +endfacet +facet normal 0 0 0 +outer loop + vertex 0 0 1 + vertex 1 0 1 + vertex 0 1 1 +endloop +endfacet +facet normal 0 0 0 +outer loop + vertex 1 0 1 + vertex 1 1 1 + vertex 0 1 1 +endloop +endfacet +facet normal 0 0 0 +outer loop + vertex 1 0 1 + vertex 1 0 0 + vertex 1 1 1 +endloop +endfacet +facet normal 0 0 0 +outer loop + vertex 1 0 0 + vertex 1 1 0 + vertex 1 1 1 +endloop +endfacet +facet normal 0 0 0 +outer loop + vertex 1 0 0 + vertex 0 0 0 + vertex 1 1 0 +endloop +endfacet +facet normal 0 0 0 +outer loop + vertex 0 0 0 + vertex 0 1 0 + vertex 1 1 0 +endloop +endfacet +facet normal 0 0 0 +outer loop + vertex 1 1 1 + vertex 1 1 0 + vertex 0 1 1 +endloop +endfacet +facet normal 0 0 0 +outer loop + vertex 1 1 0 + vertex 0 1 0 + vertex 0 1 1 +endloop +endfacet +facet normal 0 0 0 +outer loop + vertex 0 0 1 + vertex 0 0 0 + vertex 1 0 1 +endloop +endfacet +facet normal 0 0 0 +outer loop + vertex 0 0 0 + vertex 1 0 0 + vertex 1 0 1 +endloop +endfacet +endsolid +""" + +# In python 3 convert the text from unicode to a "classic" str +if sys.version_info[0] >= 3 : + cube = cube.encode("ascii") + +fd, filename = tempfile.mkstemp(suffix=".stl") +os.write(fd, cube) +os.close(fd) + +s = mshr.Surface3D(filename) +d = mshr.CSGCGALDomain3D(s) + +os.remove(filename) + +assert d.num_holes() == 0 diff --git a/test/test-mesh-generation.py b/test/test-mesh-generation.py new file mode 100644 index 0000000..7c6cfba --- /dev/null +++ b/test/test-mesh-generation.py @@ -0,0 +1,10 @@ +import mshr +from dolfin import * + +# issue 37 +g = mshr.Rectangle(Point(0.0, 0.0), Point(2.2, .41)) - mshr.Circle(Point(.2, .2), .05, 40) +m = mshr.generate_mesh(g, 50) + +# issue 41 (failed only in parallel) +c = mshr.Extrude2D(mshr.Circle(Point(0, 0, 0), 1), 1) +m = mshr.generate_mesh(c, 10) diff --git a/test/test-meshes.py b/test/test-meshes.py new file mode 100644 index 0000000..20be703 --- /dev/null +++ b/test/test-meshes.py @@ -0,0 +1,6 @@ +from dolfin import * +import mshr + +# Unit sphere mesh +m = mshr.UnitSphereMesh(4) +m = mshr.UnitSphereMesh(10) diff --git a/test/test-num-segments-2d.py b/test/test-num-segments-2d.py new file mode 100644 index 0000000..e1c2273 --- /dev/null +++ b/test/test-num-segments-2d.py @@ -0,0 +1,13 @@ +from dolfin import * +import mshr + +# The ellipse is completely contained in the circle and will not be +# visible in the resulting mesh, but it is so small that with +# mesh_resolution = 10 that when converting it to a polygon the number +# of segments in the polygon will be 0 (ie. the polygon is degenerate) +# if not handled correctly. +c = mshr.Circle(Point(0, 0), 1.5) +e = mshr.Ellipse(Point(.05, 0), .01, .05) + +mesh = mshr.generate_mesh(c+e, 10) + diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 0000000..19d17d1 --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,142 @@ +// Copyright (C) 2013 Benjamin Kehlet +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with DOLFIN. If not, see . +// +// First added: 2013-10-03 +// Last changed: 2013-10-03 + +#include +#include +#include +#include +#include +#include + +typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel; +typedef Kernel::Point_3 Point; +typedef Kernel::Triangle_3 Triangle; +typedef CGAL::Polyhedron_3 Polyhedron; +typedef typename Polyhedron::Facet_handle Facet_handle; +typedef typename Polyhedron::Halfedge_handle Halfedge_handle; +typedef typename Kernel::Segment_3 Segment; + +void two_tetrahedrons() +{ + Polyhedron a; + + make_tetrahedron(a, + Point(1.0, 0.0, 0.0), + Point(2.0, 0.0, 0.0), + Point(1.5, 1.0, 0.0), + Point(1.5, .5, 10.0)); + + Polyhedron b; + make_tetrahedron(b, + Point(0.0, 0., .5), + Point(0.0, 0.0, 1.5), + Point(0.0, 1.0, 1.0), + Point(10.0, .5, 1.0)); + + if (a.is_pure_triangle()) + std::cout << "a is pure triangle" << std::endl; + + if (b.is_pure_triangle()) + std::cout << "b is pure triangle" << std::endl; + + Polyhedron &biggest = a.size_of_facets() > b.size_of_facets() ? a : b; + Polyhedron &smallest = a.size_of_facets() > b.size_of_facets() ? b : a; + + std::list > > polylines; + { + std::list > intersections; + compute_intersections(biggest, smallest, std::back_inserter(intersections)); + + for (std::list >::iterator it = intersections.begin(); + it != intersections.end(); it++) + { + { + Halfedge_handle h = it->get<0>()->halfedge(); + Triangle t(h->vertex()->point(), h->next()->vertex()->point(), h->next()->next()->vertex()->point()); + assert(t.has_on(it->get<2>().source())); + assert(t.has_on(it->get<2>().target())); + } + { + Halfedge_handle h = it->get<1>()->halfedge(); + Triangle t(h->vertex()->point(), h->next()->vertex()->point(), h->next()->next()->vertex()->point()); + assert(t.has_on(it->get<2>().source())); + assert(t.has_on(it->get<2>().target())); + } + } + sort_polylines(biggest, smallest, intersections, polylines); + } + + std::list > intersection_list; + + split_facets(biggest, polylines, intersection_list); + //split_facets(smallest, polylines); + +} + +void two_boxes() +{ + Polyhedron a; + make_box(0,0,0, 4, 5, 2, a); + + Polyhedron b; + make_box(1, 1, -1, 2, 2, 1, b); + + if (a.is_pure_triangle()) + std::cout << "a is pure triangle" << std::endl; + + if (b.is_pure_triangle()) + std::cout << "b is pure triangle" << std::endl; + + Polyhedron &biggest = a.size_of_facets() > b.size_of_facets() ? a : b; + Polyhedron &smallest = a.size_of_facets() > b.size_of_facets() ? b : a; + + std::list > > polylines; + { + std::list > intersections; + compute_intersections(biggest, smallest, std::back_inserter(intersections)); + + for (std::list >::iterator it = intersections.begin(); + it != intersections.end(); it++) + { + { + Halfedge_handle h = it->get<0>()->halfedge(); + Triangle t(h->vertex()->point(), h->next()->vertex()->point(), h->next()->next()->vertex()->point()); + assert(t.has_on(it->get<2>().source())); + assert(t.has_on(it->get<2>().target())); + } + { + Halfedge_handle h = it->get<1>()->halfedge(); + Triangle t(h->vertex()->point(), h->next()->vertex()->point(), h->next()->next()->vertex()->point()); + assert(t.has_on(it->get<2>().source())); + assert(t.has_on(it->get<2>().target())); + } + } + sort_polylines(biggest, smallest, intersections, polylines); + } + + std::list > a_edges; + split_facets(biggest, polylines, a_edges); + check_splitting(biggest, polylines, a_edges); + //split_facets(smallest, /* smallest, */ polylines); +} + +int main(int argc, char** argv) +{ + two_boxes(); + return 0; +} diff --git a/test/test2.cpp b/test/test2.cpp new file mode 100644 index 0000000..1c375c4 --- /dev/null +++ b/test/test2.cpp @@ -0,0 +1,54 @@ +// Copyright (C) 2012 Anders Logg +// +// This file is part of DOLFIN. +// +// DOLFIN is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// DOLFIN is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with DOLFIN. If not, see . +// +// Modified by Benjamin Kehlet, 2012 +// Modified by Johannes Ring, 2012 +// Modified by Joachim B Haga, 2012 + +#include +#include + +int main(int argc, char** argv) +{ + // Define 3D geometry + mshr::Box box(0, 0, 0, 1, 1, 1); + mshr::Sphere sphere(dolfin::Point(0, 0, 0), 0.3); + mshr::Cone cone(dolfin::Point(0, 0, -1), dolfin::Point(0, 0, 1), .5, .5); + + const boost::shared_ptr g3d = box + cone - sphere; + + // Test printing + dolfin::info("\nCompact output of 3D geometry:"); + dolfin::info(*g3d); + dolfin::info("\nVerbose output of 3D geometry:"); + dolfin::info(*g3d, true); + + // Plot geometry + //dolfin::plot(g3d, "3D geometry (surface)"); + + // Generate and plot mesh + dolfin::Mesh mesh3d; + + mshr::CSGMeshGenerator::generate(mesh3d, *g3d, 24); + dolfin::cout << "Done generating mesh" << dolfin::endl; + dolfin::info(mesh3d); + dolfin::plot(mesh3d, "3D mesh"); + + dolfin::interactive(); + + return 0; +} diff --git a/test/test3D.cpp b/test/test3D.cpp new file mode 100644 index 0000000..5b8f961 --- /dev/null +++ b/test/test3D.cpp @@ -0,0 +1,51 @@ +// Copyright (C) 2012 Anders Logg +// +// This file is part of mshr. +// +// mshr is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// mshr is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with mshr. If not, see . +// + +#include +#include + +int main(int argc, char** argv) +{ + // Define 3D geometry + mshr::Box box(0, 0, 0, 1, 1, 1); + mshr::Sphere sphere(dolfin::Point(0, 0, 0), 0.3); + mshr::Cone cone(dolfin::Point(0, 0, -1), dolfin::Point(0, 0, 1), .5, .5); + + const boost::shared_ptr g3d = box + cone - sphere; + + // Test printing + dolfin::info("\nCompact output of 3D geometry:"); + dolfin::info(*g3d); + dolfin::info("\nVerbose output of 3D geometry:"); + dolfin::info(*g3d, true); + + // Plot geometry + //dolfin::plot(g3d, "3D geometry (surface)"); + + // Generate and plot mesh + dolfin::Mesh mesh3d; + + mshr::CSGMeshGenerator::generate(mesh3d, *g3d, 24); + dolfin::cout << "Done generating mesh" << dolfin::endl; + dolfin::info(mesh3d); + dolfin::plot(mesh3d, "3D mesh"); + + dolfin::interactive(); + + return 0; +} diff --git a/use-mshr.cmake b/use-mshr.cmake new file mode 100644 index 0000000..1062c30 --- /dev/null +++ b/use-mshr.cmake @@ -0,0 +1,24 @@ +# +# This file sets up include directories, link directories, and +# compiler settings for a project to use mshr. It should not be +# included directly, but rather through the DOLFIN_USE_FILE setting +# obtained from mshr-config.cmake. +# + +if (NOT MSHR_USE_FILE_INCLUDED) + set(MSHR_USE_FILE_INCLUDED 1) + + # Add compiler definitions needed to use mshr + add_definitions(${MSHR_CXX_DEFINITIONS}) + + # Add compiler flags needed to use MSHR + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MSHR_CXX_FLAGS}") + + # Add include directories needed to use MSHR + include_directories(${MSHR_INCLUDE_DIRS}) + include_directories(SYSTEM ${MSHR_EXTERNAL_INCLUDE_DIRS}) + + # Add link directories needed to use MSHR + message(STATUS "Setting link directories") + link_directories(${MSHR_LIBRARIES_DIRS}) +endif() -- 2.30.2